From 41a74f46c8e8751d2c66791c7d6dba1eaa7b6ab9 Mon Sep 17 00:00:00 2001 From: Colin Powell Date: Thu, 5 Jan 2023 13:39:56 -0500 Subject: [PATCH] Add scrobble log option and CLI tool --- pyproject.toml | 4 +- .../migrations/0004_scrobble_scrobble_log.py | 18 ++++++++ scrobbles/models.py | 1 + scrobbles/views.py | 46 ++++++++++++++++--- vrobbler/cli.py | 34 ++++++++++++++ vrobbler/settings.py | 7 ++- 6 files changed, 100 insertions(+), 10 deletions(-) create mode 100644 scrobbles/migrations/0004_scrobble_scrobble_log.py create mode 100644 vrobbler/cli.py diff --git a/pyproject.toml b/pyproject.toml index 0d8d45b..2ff1fb2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,9 +62,7 @@ combine_as_imports = true exclude_dirs = ["*/tests/*", "*/migrations/*"] [tool.poetry.scripts] -server = 'scripts:server' -migrate = 'scripts:migrate' -shell = 'scripts:shell' +vrobbler = "vrobbler.cli:main" [build-system] requires = ["poetry-core>=1.0.0"] diff --git a/scrobbles/migrations/0004_scrobble_scrobble_log.py b/scrobbles/migrations/0004_scrobble_scrobble_log.py new file mode 100644 index 0000000..8e045c3 --- /dev/null +++ b/scrobbles/migrations/0004_scrobble_scrobble_log.py @@ -0,0 +1,18 @@ +# Generated by Django 4.1.5 on 2023-01-05 17:50 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('scrobbles', '0003_remove_scrobble_counted_scrobble_in_progress'), + ] + + operations = [ + migrations.AddField( + model_name='scrobble', + name='scrobble_log', + field=models.TextField(blank=True, null=True), + ), + ] diff --git a/scrobbles/models.py b/scrobbles/models.py index 108dac0..33cb2b9 100644 --- a/scrobbles/models.py +++ b/scrobbles/models.py @@ -21,6 +21,7 @@ class Scrobble(TimeStampedModel): source = models.CharField(max_length=255, **BNULL) source_id = models.TextField(**BNULL) in_progress = models.BooleanField(default=True) + scrobble_log = models.TextField(**BNULL) @property def percent_played(self) -> int: diff --git a/scrobbles/views.py b/scrobbles/views.py index 7b64e45..dae4150 100644 --- a/scrobbles/views.py +++ b/scrobbles/views.py @@ -34,7 +34,9 @@ class RecentScrobbleList(ListView): model = Scrobble def get_queryset(self): - return Scrobble.objects.filter(in_progress=False) + return Scrobble.objects.filter(in_progress=False).order_by( + '-timestamp' + ) @csrf_exempt @@ -93,8 +95,16 @@ def jellyfin_websocket(request): .order_by('-modified') .first() ) + existing_in_progress_scrobble = ( + Scrobble.objects.filter( + video=video, user_id=request.user.id, in_progress=True + ) + .order_by('-modified') + .first() + ) minutes_from_now = timezone.now() + timedelta(minutes=15) + a_day_from_now = timezone.now() + timedelta(days=1) if ( existing_finished_scrobble @@ -105,14 +115,28 @@ def jellyfin_websocket(request): ) return Response(video_dict, status=status.HTTP_204_NO_CONTENT) - scrobble, scrobble_created = Scrobble.objects.get_or_create( - **scrobble_dict - ) + # Check if found in progress scrobble is more than a day old + if not ( + existing_in_progress_scrobble + and existing_in_progress_scrobble.modified < a_day_from_now + ): + logger.info( + 'Found a scrobble for this video more than a day old, creating a new scrobble' + ) + scrobble = existing_in_progress_scrobble + scrobble_created = False + else: + if getattr(settings, "DELETE_STALE_SCROBBLES", True): + existing_in_progress_scrobble.delete() + scrobble, scrobble_created = Scrobble.objects.get_or_create( + **scrobble_dict + ) if scrobble_created: # If we newly created this, capture the client we're watching from scrobble.source = data_dict['ClientName'] scrobble.source_id = data_dict['MediaSourceId'] + scrobble.scrobble_log = "" else: last_tick = scrobble.playback_position_ticks @@ -121,7 +145,14 @@ def jellyfin_websocket(request): scrobble.playback_position = data_dict["PlaybackPosition"] scrobble.timestamp = parse(data_dict["UtcTimestamp"]) scrobble.is_paused = data_dict["IsPaused"] in TRUTHY_VALUES - scrobble.save() + scrobble.save( + update_fields=[ + 'playback_position_ticks', + 'playback_position', + 'timestamp', + 'is_paused', + ] + ) # If we hit our completion threshold, save it and get ready # to scrobble again if we re-watch this. @@ -133,6 +164,9 @@ def jellyfin_websocket(request): scrobble.save() if scrobble.percent_played % 5 == 0: - logger.info(f"You are {scrobble.percent_played}% through {video}") + 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']) + logger.debug(f"You are {scrobble.percent_played}% through {video}") return Response(video_dict, status=status.HTTP_201_CREATED) diff --git a/vrobbler/cli.py b/vrobbler/cli.py new file mode 100644 index 0000000..3addf6e --- /dev/null +++ b/vrobbler/cli.py @@ -0,0 +1,34 @@ +# cli.py +import logging +import sys +from os import environ as env + + +if not 'DJANGO_SETTINGS_MODULE' in env: + from vrobbler import settings + env.setdefault('DJANGO_SETTINGS_MODULE', settings.__name__) + + +import django +django.setup() + +# this line must be after django.setup() for logging configure +logger = logging.getLogger('vrobbler') + +def main(): + # to get configured settings + from django.conf import settings + + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() diff --git a/vrobbler/settings.py b/vrobbler/settings.py index a668713..a6a7d01 100644 --- a/vrobbler/settings.py +++ b/vrobbler/settings.py @@ -32,6 +32,12 @@ DEBUG = os.getenv("VROBBLER_DEBUG", False) TESTING = len(sys.argv) > 1 and sys.argv[1] == "test" +KEEP_DETAILED_SCROBBLE_LOGS = os.getenv( + "VROBBLER_KEEP_DETAILED_SCROBBLE_LOGS", False +) + +DELETE_STALE_SCROBBLES = os.getenv("VROBBLER_DELETE_STALE_SCROBBLES", True) + TMDB_API_KEY = os.getenv("VROBBLER_TMDB_API_KEY", "") DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' @@ -61,7 +67,6 @@ INSTALLED_APPS = [ "django_filters", "django_extensions", 'rest_framework.authtoken', - "vrobbler", "scrobbles", "videos", "rest_framework",