|
@@ -72,60 +72,6 @@ class Scrobble(TimeStampedModel):
|
|
|
def __str__(self):
|
|
|
return f"Scrobble of {self.media_obj} {self.timestamp.year}-{self.timestamp.month}"
|
|
|
|
|
|
- def resumable(self, playback_ticks):
|
|
|
- """Check if a scrobble is not finished or beyond the configured resume limit.
|
|
|
-
|
|
|
- The idea here is to check whether a scrobble should be resumed, or a new
|
|
|
- one created. If this method returns true, we should update an existing
|
|
|
- scrobble, suggesting the user just paused their scrobble. This limit
|
|
|
- should be different for different media. We are more likely to pause a video
|
|
|
- or sports event for a while, and expect to resume it than an audio track or
|
|
|
- a podcast.
|
|
|
-
|
|
|
- """
|
|
|
- diff = None
|
|
|
- # Default finish expectation
|
|
|
- percent_for_completion = 100
|
|
|
- # By default, assume we're not beyond resume limits
|
|
|
- # This is to avoid spam scrobbles if webhooks go crazy
|
|
|
- beyond_resume_limit = False
|
|
|
- now = timezone.now()
|
|
|
-
|
|
|
- if self.playback_position_ticks == playback_ticks:
|
|
|
- # shortcircut in the case where we've resumed a track at the same playback ticks
|
|
|
- return True
|
|
|
-
|
|
|
- if self.video:
|
|
|
- diff = timedelta(seconds=Video.RESUME_LIMIT)
|
|
|
- percent_for_completion = Video.COMPLETION_PERCENT
|
|
|
- if self.track:
|
|
|
- diff = timedelta(seconds=Track.RESUME_LIMIT)
|
|
|
- percent_for_completion = Track.COMPLETION_PERCENT
|
|
|
- if self.podcast_episode:
|
|
|
- diff = timedelta(seconds=Episode.RESUME_LIMIT)
|
|
|
- percent_for_completion = Episode.COMPLETION_PERCENT
|
|
|
- if self.sport_event:
|
|
|
- diff = timedelta(seconds=SportEvent.RESUME_LIMIT)
|
|
|
- percent_for_completion = SportEvent.COMPLETION_PERCENT
|
|
|
-
|
|
|
- if diff and self.timestamp:
|
|
|
- beyond_resume_limit = self.timestamp + diff <= now
|
|
|
-
|
|
|
- finished = self.percent_played >= percent_for_completion
|
|
|
-
|
|
|
- resumable = not finished or not beyond_resume_limit
|
|
|
-
|
|
|
- if not finished:
|
|
|
- logger.debug(
|
|
|
- f"{self} resumable, percent played {self.percent_played} is less than {percent_for_completion}"
|
|
|
- )
|
|
|
- if not beyond_resume_limit:
|
|
|
- logger.debug(
|
|
|
- f"{self} resumable, started less than {diff.seconds} seconds ago"
|
|
|
- )
|
|
|
-
|
|
|
- return not finished and not beyond_resume_limit
|
|
|
-
|
|
|
@classmethod
|
|
|
def create_or_update_for_video(
|
|
|
cls, video: "Video", user_id: int, scrobble_data: dict
|
|
@@ -133,13 +79,15 @@ class Scrobble(TimeStampedModel):
|
|
|
scrobble_data['video_id'] = video.id
|
|
|
|
|
|
scrobble = (
|
|
|
- cls.objects.filter(video=video, user_id=user_id)
|
|
|
+ cls.objects.filter(
|
|
|
+ video=video,
|
|
|
+ user_id=user_id,
|
|
|
+ played_to_completion=False,
|
|
|
+ )
|
|
|
.order_by('-modified')
|
|
|
.first()
|
|
|
)
|
|
|
- if scrobble and scrobble.resumable(
|
|
|
- scrobble_data['playback_position_ticks']
|
|
|
- ):
|
|
|
+ if scrobble:
|
|
|
logger.info(
|
|
|
f"Found existing scrobble for video {video}, updating",
|
|
|
{"scrobble_data": scrobble_data},
|
|
@@ -164,13 +112,15 @@ class Scrobble(TimeStampedModel):
|
|
|
scrobble_data['track_id'] = track.id
|
|
|
|
|
|
scrobble = (
|
|
|
- cls.objects.filter(track=track, user_id=user_id)
|
|
|
+ cls.objects.filter(
|
|
|
+ track=track,
|
|
|
+ user_id=user_id,
|
|
|
+ played_to_completion=False,
|
|
|
+ )
|
|
|
.order_by('-modified')
|
|
|
.first()
|
|
|
)
|
|
|
- if scrobble and scrobble.resumable(
|
|
|
- scrobble_data['playback_position_ticks']
|
|
|
- ):
|
|
|
+ if scrobble:
|
|
|
logger.debug(
|
|
|
f"Found existing scrobble for track {track}, updating",
|
|
|
{"scrobble_data": scrobble_data},
|
|
@@ -192,13 +142,15 @@ class Scrobble(TimeStampedModel):
|
|
|
scrobble_data['podcast_episode_id'] = episode.id
|
|
|
|
|
|
scrobble = (
|
|
|
- cls.objects.filter(podcast_episode=episode, user_id=user_id)
|
|
|
+ cls.objects.filter(
|
|
|
+ podcast_episode=episode,
|
|
|
+ user_id=user_id,
|
|
|
+ played_to_completion=False,
|
|
|
+ )
|
|
|
.order_by('-modified')
|
|
|
.first()
|
|
|
)
|
|
|
- if scrobble and scrobble.resumable(
|
|
|
- scrobble_data['playback_position_ticks']
|
|
|
- ):
|
|
|
+ if scrobble:
|
|
|
logger.debug(
|
|
|
f"Found existing scrobble for podcast {episode}, updating",
|
|
|
{"scrobble_data": scrobble_data},
|
|
@@ -219,13 +171,15 @@ class Scrobble(TimeStampedModel):
|
|
|
) -> "Scrobble":
|
|
|
scrobble_data['sport_event_id'] = event.id
|
|
|
scrobble = (
|
|
|
- cls.objects.filter(sport_event=event, user_id=user_id)
|
|
|
+ cls.objects.filter(
|
|
|
+ sport_event=event,
|
|
|
+ user_id=user_id,
|
|
|
+ played_to_completion=False,
|
|
|
+ )
|
|
|
.order_by('-modified')
|
|
|
.first()
|
|
|
)
|
|
|
- if scrobble and scrobble.resumable(
|
|
|
- scrobble_data['playback_position_ticks']
|
|
|
- ):
|
|
|
+ if scrobble:
|
|
|
logger.debug(
|
|
|
f"Found existing scrobble for sport event {event}, updating",
|
|
|
{"scrobble_data": scrobble_data},
|
|
@@ -246,8 +200,6 @@ class Scrobble(TimeStampedModel):
|
|
|
scrobble_status = scrobble_data.pop('mopidy_status', None)
|
|
|
if not scrobble_status:
|
|
|
scrobble_status = scrobble_data.pop('jellyfin_status', None)
|
|
|
- if not scrobble_status:
|
|
|
- scrobble_status = "resumed"
|
|
|
|
|
|
logger.debug(f"Scrobbling to {scrobble} with status {scrobble_status}")
|
|
|
scrobble.update_ticks(scrobble_data)
|