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}}