Browse Source

Dramatically simplify the scrobblig code

Colin Powell 2 năm trước cách đây
mục cha
commit
3f8b29f5ee

+ 2 - 2
vrobbler/apps/music/aggregators.py

@@ -18,7 +18,7 @@ STARTING_DAY_OF_CURRENT_YEAR = NOW.date().replace(month=1, day=1)
 
 
 def scrobble_counts():
-    finished_scrobbles_qs = Scrobble.objects.filter(in_progress=False)
+    finished_scrobbles_qs = Scrobble.objects.filter(played_to_completion=True)
     data = {}
     data['today'] = finished_scrobbles_qs.filter(
         timestamp__gte=START_OF_TODAY
@@ -53,7 +53,7 @@ def week_of_scrobbles(media: str = 'tracks') -> dict[str, int]:
             .filter(
                 timestamp__gte=start,
                 timestamp__lte=end,
-                in_progress=False,
+                played_to_completion=True,
             )
             .count()
         )

+ 17 - 0
vrobbler/apps/music/migrations/0008_alter_track_options.py

@@ -0,0 +1,17 @@
+# Generated by Django 4.1.5 on 2023-01-13 01:47
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('music', '0007_alter_album_artists'),
+    ]
+
+    operations = [
+        migrations.AlterModelOptions(
+            name='track',
+            options={},
+        ),
+    ]

+ 32 - 27
vrobbler/apps/music/models.py

@@ -48,35 +48,38 @@ class Album(TimeStampedModel):
         return self.artists.first()
 
     def fix_metadata(self):
-        musicbrainzngs.set_useragent('vrobbler', '0.3.0')
-        mb_data = musicbrainzngs.get_release_by_id(
-            self.musicbrainz_id, includes=['artists']
-        )
-        if not self.musicbrainz_albumartist_id:
-            self.musicbrainz_albumartist_id = mb_data['release'][
-                'artist-credit'
-            ][0]['artist']['id']
-        if not self.year:
-            self.year = mb_data['release']['date'][0:4]
-        self.save(update_fields=['musicbrainz_albumartist_id', 'year'])
-
-        new_artist = Artist.objects.filter(
-            musicbrainz_id=self.musicbrainz_albumartist_id
-        ).first()
-        if self.musicbrainz_albumartist_id and new_artist:
-            self.artists.add(new_artist)
-        if not new_artist:
-            for t in self.track_set.all():
-                self.artists.add(t.artist)
+        if not self.musicbrainz_albumartist_id or not self.year:
+            musicbrainzngs.set_useragent('vrobbler', '0.3.0')
+            mb_data = musicbrainzngs.get_release_by_id(
+                self.musicbrainz_id, includes=['artists']
+            )
+            if not self.musicbrainz_albumartist_id:
+                self.musicbrainz_albumartist_id = mb_data['release'][
+                    'artist-credit'
+                ][0]['artist']['id']
+            if not self.year:
+                self.year = mb_data['release']['date'][0:4]
+            self.save(update_fields=['musicbrainz_albumartist_id', 'year'])
+
+            new_artist = Artist.objects.filter(
+                musicbrainz_id=self.musicbrainz_albumartist_id
+            ).first()
+            if self.musicbrainz_albumartist_id and new_artist:
+                self.artists.add(new_artist)
+            if not new_artist:
+                for t in self.track_set.all():
+                    self.artists.add(t.artist)
 
     def fetch_artwork(self):
-        try:
-            img_data = musicbrainzngs.get_image_front(self.musicbrainz_id)
-            name = f"{self.name}_{self.uuid}.jpg"
-            self.cover_image = ContentFile(img_data, name=name)
+        if not self.cover_image:
+            try:
+                img_data = musicbrainzngs.get_image_front(self.musicbrainz_id)
+                name = f"{self.name}_{self.uuid}.jpg"
+                self.cover_image = ContentFile(img_data, name=name)
+            except musicbrainzngs.ResponseError:
+                logger.warning(f'No cover art found for {self.name}')
+                self.cover_image = 'default-image-replace-me'
             self.save()
-        except musicbrainzngs.ResponseError:
-            logger.warning(f'No cover art found for {self.name}')
 
     @property
     def mb_link(self):
@@ -132,8 +135,10 @@ class Track(ScrobblableMixin):
             logger.debug(f"Created new album {album}")
         else:
             logger.debug(f"Found album {album}")
+
         album.fix_metadata()
-        album.fetch_artwork()
+        if not album.cover_image:
+            album.fetch_artwork()
 
         track_dict['album_id'] = getattr(album, "id", None)
         track_dict['artist_id'] = artist.id

+ 22 - 0
vrobbler/apps/podcasts/migrations/0005_alter_episode_options_alter_episode_title.py

@@ -0,0 +1,22 @@
+# Generated by Django 4.1.5 on 2023-01-13 01:47
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('podcasts', '0004_episode_number'),
+    ]
+
+    operations = [
+        migrations.AlterModelOptions(
+            name='episode',
+            options={},
+        ),
+        migrations.AlterField(
+            model_name='episode',
+            name='title',
+            field=models.CharField(blank=True, max_length=255, null=True),
+        ),
+    ]

+ 5 - 1
vrobbler/apps/scrobbles/admin.py

@@ -10,10 +10,11 @@ class ScrobbleAdmin(admin.ModelAdmin):
         "timestamp",
         "media_name",
         "media_type",
+        "playback_percent",
         "source",
-        "playback_position",
         "in_progress",
         "is_paused",
+        "played_to_completion",
     )
     list_filter = ("is_paused", "in_progress", "source", "track__artist")
     ordering = ("-timestamp",)
@@ -33,3 +34,6 @@ class ScrobbleAdmin(admin.ModelAdmin):
             return "Track"
         if obj.podcast_episode:
             return "Podcast"
+
+    def playback_percent(self, obj):
+        return obj.percent_played

+ 55 - 72
vrobbler/apps/scrobbles/models.py

@@ -45,7 +45,7 @@ class Scrobble(TimeStampedModel):
         if (
             self.playback_position_ticks
             and self.media_obj.run_time_ticks
-            and source != 'Mopidy'
+            and self.source != 'Mopidy'
         ):
             return int(
                 (self.playback_position_ticks / self.media_obj.run_time_ticks)
@@ -61,26 +61,6 @@ class Scrobble(TimeStampedModel):
 
         return 0
 
-    def is_stale(self, backoff, wait_period) -> bool:
-        scrobble_is_stale = self.in_progress and self.modified > wait_period
-
-        # Check if found in progress scrobble is more than a day old
-        if scrobble_is_stale:
-            logger.info(
-                'Found a in-progress scrobble for this item more than a day old, creating a new scrobble'
-            )
-            delete_stale_scrobbles = getattr(
-                settings, "DELETE_STALE_SCROBBLES", True
-            )
-
-            if delete_stale_scrobbles:
-                logger.info(
-                    'Deleting {scrobble} that has been in-progress too long'
-                )
-                self.delete()
-
-        return scrobble_is_stale
-
     @property
     def media_obj(self):
         media_obj = None
@@ -171,60 +151,31 @@ class Scrobble(TimeStampedModel):
     ) -> Optional["Scrobble"]:
 
         # Status is a field we get from Mopidy, which refuses to poll us
-        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_status = scrobble_data.pop('mopidy_status', None)
+        if not scrobble_status:
+            scrobble_status = scrobble_data.pop('jellyfin_status', None)
+        if not scrobble_status:
+            logger.warning(
+                f"No status update found in message, not scrobbling"
             )
-            scrobble.save(update_fields=['playback_position_ticks'])
+            return
 
-        if mopidy_status == "stopped":
-            logger.info(f"Mopidy sent a message to stop {scrobble}")
-            if not scrobble:
-                logger.warning(
-                    'Mopidy sent us a stopped message, without ever starting'
-                )
-                return
+        logger.debug(f"Scrobbling to {scrobble} with status {scrobble_status}")
+        if scrobble:
+            scrobble.update_ticks(scrobble_data)
 
-            # Mopidy finished a play, scrobble away
-            scrobble.in_progress = False
-            scrobble.played_to_completion = True
-            scrobble.save(
-                update_fields=['in_progress', 'played_to_completion']
-            )
-            return scrobble
+            # On stop, stop progress and send it to the check for completion
+            if scrobble_status == "stopped":
+                return scrobble.stop()
 
-        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
+            # On pause, set is_paused and stop scrobbling
+            if scrobble_status == "paused":
+                return scrobble.pause()
 
-            # 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 scrobble_status == "resumed":
+                return scrobble.resume()
 
-        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:
+            # We're not changing the scrobble, but we don't want to walk over an existing one
             scrobble_is_finished = (
                 not scrobble.in_progress and scrobble.modified < backoff
             )
@@ -234,9 +185,7 @@ class Scrobble(TimeStampedModel):
                 )
                 return
 
-            scrobble_is_stale = scrobble.is_stale(backoff, wait_period)
-
-        if (not scrobble or scrobble_is_stale) or mopidy_status:
+        if not scrobble:
             # If we default this to "" we can probably remove this
             scrobble_data['scrobble_log'] = ""
             scrobble = cls.objects.create(
@@ -252,3 +201,37 @@ class Scrobble(TimeStampedModel):
         scrobble = check_scrobble_for_finish(scrobble)
 
         return scrobble
+
+    def stop(self):
+        if not self.in_progress:
+            logger.warning("Scrobble already stopped")
+            return
+        self.in_progress = False
+        self.save(update_fields=['in_progress'])
+        return check_scrobble_for_finish(self)
+
+    def pause(self):
+        if self.is_paused:
+            logger.warning("Scrobble already paused")
+            return
+        self.is_paused = True
+        self.save(update_fields=["is_paused"])
+        return check_scrobble_for_finish(self)
+
+    def resume(self):
+        if self.is_paused or not self.in_progress:
+            self.is_paused = False
+            self.in_progress = True
+            return self.save(update_fields=["is_paused", "in_progress"])
+        return self
+
+    def update_ticks(self, data):
+        self.playback_position_ticks = data.get("playback_position_ticks")
+        self.playback_position = data.get("playback_position")
+        logger.debug(
+            f"Updating scrobble ticks to {self.playback_position_ticks}"
+        )
+        self.save(
+            update_fields=['playback_position_ticks', 'playback_position']
+        )
+        return self

+ 88 - 3
vrobbler/apps/scrobbles/scrobblers.py

@@ -1,11 +1,14 @@
 import logging
 from typing import Optional
 
+from dateutil.parser import parse
 from django.utils import timezone
+from music.constants import JELLYFIN_POST_KEYS
 from music.models import Track
 from podcasts.models import Episode
 from scrobbles.models import Scrobble
-from scrobbles.utils import parse_mopidy_uri
+from scrobbles.utils import convert_to_seconds, parse_mopidy_uri
+from videos.models import Video
 
 logger = logging.getLogger(__name__)
 
@@ -41,7 +44,7 @@ def mopidy_scrobble_podcast(
         "timestamp": timezone.now(),
         "playback_position_ticks": data_dict.get("playback_time_ticks"),
         "source": "Mopidy",
-        "status": data_dict.get("status"),
+        "mopidy_status": data_dict.get("status"),
     }
 
     scrobble = None
@@ -79,10 +82,11 @@ def mopidy_scrobble_track(
         "timestamp": timezone.now(),
         "playback_position_ticks": data_dict.get("playback_time_ticks"),
         "source": "Mopidy",
-        "status": data_dict.get("status"),
+        "mopidy_status": data_dict.get("status"),
     }
 
     scrobble = None
+
     if track:
         # Jellyfin MB ids suck, so always overwrite with Mopidy if they're offering
         track.musicbrainz_id = data_dict.get("musicbrainz_track_id")
@@ -91,3 +95,84 @@ def mopidy_scrobble_track(
             track, user_id, mopidy_data
         )
     return scrobble
+
+
+def create_jellyfin_scrobble_dict(data_dict: dict, user_id: int) -> dict:
+    jellyfin_status = "resumed"
+    if data_dict.get("IsPaused"):
+        jellyfin_status = "paused"
+    if data_dict.get("PlayedToCompletion"):
+        jellyfin_status = "stopped"
+
+    return {
+        "user_id": user_id,
+        "timestamp": parse(data_dict.get("UtcTimestamp")),
+        "playback_position_ticks": data_dict.get("PlaybackPositionTicks")
+        // 10000,
+        "playback_position": convert_to_seconds(
+            data_dict.get("PlaybackPosition")
+        ),
+        "source": "Jellyfin",
+        "source_id": data_dict.get('MediaSourceId'),
+        "jellyfin_status": jellyfin_status,
+    }
+
+
+def jellyfin_scrobble_track(
+    data_dict: dict, user_id: Optional[int]
+) -> Optional[Scrobble]:
+    if not data_dict.get("Provider_musicbrainztrack", None):
+        logger.error(
+            "No MBrainz Track ID received. This is likely because all metadata is bad, not scrobbling"
+        )
+        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
+        ),
+    }
+
+    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()
+
+    scrobble_dict = create_jellyfin_scrobble_dict(data_dict, user_id)
+
+    return Scrobble.create_or_update_for_track(track, user_id, scrobble_dict)
+
+
+def jellyfin_scrobble_video(data_dict: dict, user_id: Optional[int]):
+    if not data_dict.get("Provider_imdb", None):
+        logger.error(
+            "No IMDB ID received. This is likely because all metadata is bad, not scrobbling"
+        )
+        return
+    video = Video.find_or_create(data_dict)
+
+    scrobble_dict = create_jellyfin_scrobble_dict(data_dict, user_id)
+
+    return Scrobble.create_or_update_for_video(video, user_id, scrobble_dict)

+ 6 - 13
vrobbler/apps/scrobbles/utils.py

@@ -1,5 +1,5 @@
 import logging
-from typing import Any
+from typing import Any, Optional
 from urllib.parse import unquote
 
 from dateutil.parser import ParserError, parse
@@ -61,26 +61,19 @@ def parse_mopidy_uri(uri: str) -> dict:
 
 
 def check_scrobble_for_finish(scrobble: "Scrobble") -> None:
-    completion_percent = getattr(settings, "MUSIC_COMPLETION_PERCENT", 90)
+    completion_percent = getattr(settings, "MUSIC_COMPLETION_PERCENT", 95)
+    if scrobble.video:
+        completion_percent = getattr(settings, "VIDEO_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.played_to_completion = True
         scrobble.save(
-            update_fields=[
-                "in_progress",
-                "is_paused",
-                "playback_position_ticks",
-            ]
+            update_fields=["in_progress", "is_paused", "played_to_completion"]
         )
 
     if scrobble.percent_played % 5 == 0:

+ 10 - 70
vrobbler/apps/scrobbles/views.py

@@ -18,16 +18,22 @@ from scrobbles.constants import (
     JELLYFIN_VIDEO_ITEM_TYPES,
 )
 from scrobbles.models import Scrobble
+from scrobbles.scrobblers import (
+    jellyfin_scrobble_track,
+    jellyfin_scrobble_video,
+    mopidy_scrobble_podcast,
+    mopidy_scrobble_track,
+)
 from scrobbles.serializers import ScrobbleSerializer
 from scrobbles.utils import convert_to_seconds
 from videos.models import Video
+
 from vrobbler.apps.music.aggregators import (
     scrobble_counts,
     top_artists,
     top_tracks,
     week_of_scrobbles,
 )
-from scrobbles.scrobblers import mopidy_scrobble_podcast, mopidy_scrobble_track
 
 logger = logging.getLogger(__name__)
 
@@ -55,7 +61,6 @@ class RecentScrobbleList(ListView):
         data['now_playing_list'] = Scrobble.objects.filter(
             in_progress=True,
             is_paused=False,
-            timestamp__gte=last_eight_minutes,
             timestamp__lte=now,
         )
         data['video_scrobble_list'] = Scrobble.objects.filter(
@@ -98,79 +103,14 @@ def jellyfin_websocket(request):
         json_data = json.dumps(data_dict, indent=4)
         logger.debug(f"{json_data}")
 
+    scrobble = None
     media_type = data_dict.get("ItemType", "")
 
-    track = None
-    video = None
     if media_type in JELLYFIN_AUDIO_ITEM_TYPES:
-        if not data_dict.get("Provider_musicbrainztrack", None):
-            logger.error(
-                "No MBrainz Track ID received. This is likely because all metadata is bad, not scrobbling"
-            )
-            return Response({}, status=status.HTTP_400_BAD_REQUEST)
-
-        artist_dict = {
-            'name': data_dict.get(KEYS["ARTIST_NAME"], None),
-            'musicbrainz_id': data_dict.get(KEYS["ARTIST_MB_ID"], None),
-        }
-
-        album_dict = {
-            "name": data_dict.get(KEYS["ALBUM_NAME"], None),
-            "year": data_dict.get(KEYS["YEAR"], ""),
-            "musicbrainz_id": data_dict.get(KEYS['ALBUM_MB_ID']),
-            "musicbrainz_releasegroup_id": data_dict.get(
-                KEYS["RELEASEGROUP_MB_ID"]
-            ),
-            "musicbrainz_albumartist_id": data_dict.get(KEYS["ARTIST_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(KEYS["RUN_TIME_TICKS"], None)
-            // 10000,
-            "run_time": convert_to_seconds(
-                data_dict.get(KEYS["RUN_TIME"], None)
-            ),
-        }
-        track = Track.find_or_create(artist_dict, album_dict, track_dict)
+        scrobble = jellyfin_scrobble_track(data_dict, request.user.id)
 
     if media_type in JELLYFIN_VIDEO_ITEM_TYPES:
-        if not data_dict.get("Provider_imdb", None):
-            logger.error(
-                "No IMDB ID received. This is likely because all metadata is bad, not scrobbling"
-            )
-            return Response({}, status=status.HTTP_400_BAD_REQUEST)
-        video = Video.find_or_create(data_dict)
-
-    # Now we run off a scrobble
-    jellyfin_data = {
-        "user_id": request.user.id,
-        "timestamp": parse(data_dict.get("UtcTimestamp")),
-        "playback_position_ticks": data_dict.get("PlaybackPositionTicks")
-        // 10000,
-        "playback_position": convert_to_seconds(
-            data_dict.get("PlaybackPosition")
-        ),
-        "source": "Jellyfin",
-        "source_id": data_dict.get('MediaSourceId'),
-        "is_paused": data_dict.get("IsPaused") in TRUTHY_VALUES,
-    }
-
-    scrobble = None
-    if video:
-        scrobble = Scrobble.create_or_update_for_video(
-            video, request.user.id, jellyfin_data
-        )
-    if track:
-        # Prefer Mopidy MD IDs to Jellyfin, so skip if we already have one
-        if not track.musicbrainz_id:
-            track.musicbrainz_id = data_dict.get(KEYS["TRACK_MB_ID"], None)
-            track.save()
-        scrobble = Scrobble.create_or_update_for_track(
-            track, request.user.id, jellyfin_data
-        )
+        scrobble = jellyfin_scrobble_video(data_dict, request.user.id)
 
     if not scrobble:
         return Response({}, status=status.HTTP_400_BAD_REQUEST)

+ 18 - 0
vrobbler/apps/videos/migrations/0006_alter_video_year.py

@@ -0,0 +1,18 @@
+# Generated by Django 4.1.5 on 2023-01-13 01:47
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('videos', '0005_alter_video_options_alter_video_unique_together'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='video',
+            name='year',
+            field=models.IntegerField(blank=True, null=True),
+        ),
+    ]

+ 17 - 11
vrobbler/apps/videos/models.py

@@ -44,7 +44,7 @@ class Video(ScrobblableMixin):
     )
     overview = models.TextField(**BNULL)
     tagline = models.TextField(**BNULL)
-    year = models.IntegerField()
+    year = models.IntegerField(**BNULL)
 
     # TV show specific fields
     tv_series = models.ForeignKey(Series, on_delete=models.DO_NOTHING, **BNULL)
@@ -80,11 +80,6 @@ class Video(ScrobblableMixin):
             "title": data_dict.get("Name", ""),
             "imdb_id": data_dict.get("Provider_imdb", None),
             "video_type": Video.VideoType.MOVIE,
-            "year": data_dict.get("Year", ""),
-            "overview": data_dict.get("Overview", None),
-            "tagline": data_dict.get("Tagline", None),
-            "run_time_ticks": data_dict.get("RunTimeTicks", 0) // 10000,
-            "run_time": convert_to_seconds(data_dict.get("RunTime", "")),
         }
 
         if data_dict.get("ItemType", "") == "Episode":
@@ -97,16 +92,27 @@ class Video(ScrobblableMixin):
             else:
                 logger.debug(f"Found series {series}")
             video_dict['video_type'] = Video.VideoType.TV_EPISODE
-            video_dict["tv_series_id"] = series.id
-            video_dict["tvdb_id"] = data_dict.get("Provider_tvdb", None)
-            video_dict["tvrage_id"] = data_dict.get("Provider_tvrage", None)
-            video_dict["episode_number"] = data_dict.get("EpisodeNumber", "")
-            video_dict["season_number"] = data_dict.get("SeasonNumber", "")
 
         video, created = cls.objects.get_or_create(**video_dict)
 
+        video_extra_dict = {
+            "year": data_dict.get("Year", ""),
+            "overview": data_dict.get("Overview", None),
+            "tagline": data_dict.get("Tagline", None),
+            "run_time_ticks": data_dict.get("RunTimeTicks", 0) // 10000,
+            "run_time": convert_to_seconds(data_dict.get("RunTime", "")),
+            "tv_series_id": series.id,
+            "tvdb_id": data_dict.get("Provider_tvdb", None),
+            "tvrage_id": data_dict.get("Provider_tvrage", None),
+            "episode_number": data_dict.get("EpisodeNumber", ""),
+            "season_number": data_dict.get("SeasonNumber", ""),
+        }
+
         if created:
             logger.debug(f"Created new video: {video}")
+            for key, value in video_extra_dict.items():
+                setattr(video, key, value)
+            video.save()
         else:
             logger.debug(f"Found video {video}")
 

+ 1 - 1
vrobbler/settings.py

@@ -49,7 +49,7 @@ DELETE_STALE_SCROBBLES = os.getenv("VROBBLER_DELETE_STALE_SCROBBLES", True)
 DUMP_REQUEST_DATA = os.getenv("VROBBLER_DUMP_REQUEST_DATA", False)
 
 VIDEO_BACKOFF_MINUTES = os.getenv("VROBBLER_VIDEO_BACKOFF_MINUTES", 15)
-MUSIC_BACKOFF_SECONDS = os.getenv("VROBBLER_VIDEO_BACKOFF_SECONDS", 5)
+MUSIC_BACKOFF_SECONDS = os.getenv("VROBBLER_VIDEO_BACKOFF_SECONDS", 1)
 
 # If you stop waching or listening to a track, how long should we wait before we
 # give up on the old scrobble and start a new one? This could also be considered

+ 1 - 0
vrobbler/templates/base.html

@@ -198,6 +198,7 @@
                                 {{scrobble.media_obj.title}}<br/>
                                 {% if scrobble.track %}<em>{{scrobble.track.artist}}</em><br/>{% endif %}
                                 {% if scrobble.podcast_episode%}<em>{{scrobble.podcast_episode.podcast}}</em><br/>{% endif %}
+                                {% if scrobble.video.tv_series %}<em>{{scrobble.video.tv_series }}</em><br/>{% endif %}
                                 <small>{{scrobble.created|naturaltime}}<br/>
                                     from {{scrobble.source}}</small>
                                 <div class="progress-bar" style="margin-right:5px;">