diff --git a/vrobbler/apps/music/aggregators.py b/vrobbler/apps/music/aggregators.py
index 65554c8..3833fc9 100644
--- a/vrobbler/apps/music/aggregators.py
+++ b/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()
)
diff --git a/vrobbler/apps/music/migrations/0008_alter_track_options.py b/vrobbler/apps/music/migrations/0008_alter_track_options.py
new file mode 100644
index 0000000..6785d4f
--- /dev/null
+++ b/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={},
+ ),
+ ]
diff --git a/vrobbler/apps/music/models.py b/vrobbler/apps/music/models.py
index 2678a03..0a41266 100644
--- a/vrobbler/apps/music/models.py
+++ b/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'])
+ 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)
+ 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
diff --git a/vrobbler/apps/podcasts/migrations/0005_alter_episode_options_alter_episode_title.py b/vrobbler/apps/podcasts/migrations/0005_alter_episode_options_alter_episode_title.py
new file mode 100644
index 0000000..d301ca8
--- /dev/null
+++ b/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),
+ ),
+ ]
diff --git a/vrobbler/apps/scrobbles/admin.py b/vrobbler/apps/scrobbles/admin.py
index 63347ce..14d76ed 100644
--- a/vrobbler/apps/scrobbles/admin.py
+++ b/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
diff --git a/vrobbler/apps/scrobbles/models.py b/vrobbler/apps/scrobbles/models.py
index dccaa34..510d3e0 100644
--- a/vrobbler/apps/scrobbles/models.py
+++ b/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
+ 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"
+ )
+ return
+ logger.debug(f"Scrobbling to {scrobble} with status {scrobble_status}")
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'])
+ scrobble.update_ticks(scrobble_data)
- 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
+ # On stop, stop progress and send it to the check for completion
+ if scrobble_status == "stopped":
+ return scrobble.stop()
- # 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 pause, set is_paused and stop scrobbling
+ if scrobble_status == "paused":
+ return scrobble.pause()
- 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
+ if scrobble_status == "resumed":
+ return scrobble.resume()
- # 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:
+ # 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
diff --git a/vrobbler/apps/scrobbles/scrobblers.py b/vrobbler/apps/scrobbles/scrobblers.py
index cee6bc0..f78dd0f 100644
--- a/vrobbler/apps/scrobbles/scrobblers.py
+++ b/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)
diff --git a/vrobbler/apps/scrobbles/utils.py b/vrobbler/apps/scrobbles/utils.py
index c5914de..84cedc9 100644
--- a/vrobbler/apps/scrobbles/utils.py
+++ b/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:
diff --git a/vrobbler/apps/scrobbles/views.py b/vrobbler/apps/scrobbles/views.py
index b67c5e3..1383154 100644
--- a/vrobbler/apps/scrobbles/views.py
+++ b/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)
diff --git a/vrobbler/apps/videos/migrations/0006_alter_video_year.py b/vrobbler/apps/videos/migrations/0006_alter_video_year.py
new file mode 100644
index 0000000..e735bb9
--- /dev/null
+++ b/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),
+ ),
+ ]
diff --git a/vrobbler/apps/videos/models.py b/vrobbler/apps/videos/models.py
index 5ad10e9..ea43986 100644
--- a/vrobbler/apps/videos/models.py
+++ b/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}")
diff --git a/vrobbler/settings.py b/vrobbler/settings.py
index b78ac5b..a3d565b 100644
--- a/vrobbler/settings.py
+++ b/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
diff --git a/vrobbler/templates/base.html b/vrobbler/templates/base.html
index db5127b..543a1fc 100644
--- a/vrobbler/templates/base.html
+++ b/vrobbler/templates/base.html
@@ -198,6 +198,7 @@
{{scrobble.media_obj.title}}
{% if scrobble.track %}{{scrobble.track.artist}}
{% endif %}
{% if scrobble.podcast_episode%}{{scrobble.podcast_episode.podcast}}
{% endif %}
+ {% if scrobble.video.tv_series %}{{scrobble.video.tv_series }}
{% endif %}
{{scrobble.created|naturaltime}}
from {{scrobble.source}}