diff --git a/vrobbler/apps/podcasts/models.py b/vrobbler/apps/podcasts/models.py index 3b0c9e4..27b58cf 100644 --- a/vrobbler/apps/podcasts/models.py +++ b/vrobbler/apps/podcasts/models.py @@ -39,9 +39,7 @@ class Producer(TimeStampedModel): class Podcast(TimeStampedModel): name = models.CharField(max_length=255) uuid = models.UUIDField(default=uuid4, editable=False, **BNULL) - producer = models.ForeignKey( - Producer, on_delete=models.DO_NOTHING, **BNULL - ) + producer = models.ForeignKey(Producer, on_delete=models.DO_NOTHING, **BNULL) podcastindex_id = models.CharField(max_length=100, **BNULL) owner = models.CharField(max_length=150, *BNULL) description = models.TextField(**BNULL) @@ -60,6 +58,16 @@ class Podcast(TimeStampedModel): def get_absolute_url(self): return reverse("podcasts:podcast_detail", kwargs={"slug": self.uuid}) + @property + def safe_cover_image_url(self) -> str: + if self.cover_image: + try: + if self.cover_image.storage.exists(self.cover_image.name): + return self.cover_image.url + except Exception: + pass + return "/static/images/not-found.jpg" + def scrobbles(self, user): Scrobble = apps.get_model("scrobbles", "Scrobble") return Scrobble.objects.filter( @@ -113,9 +121,7 @@ class PodcastEpisode(ScrobblableMixin): mopidy_uri = models.CharField(max_length=255, **BNULL) def get_absolute_url(self): - return reverse( - "podcasts:podcast_detail", kwargs={"slug": self.podcast.uuid} - ) + return reverse("podcasts:podcast_detail", kwargs={"slug": self.podcast.uuid}) def __str__(self): return f"{self.title}" diff --git a/vrobbler/apps/scrobbles/scrobblers.py b/vrobbler/apps/scrobbles/scrobblers.py index a71e1b8..78921e3 100644 --- a/vrobbler/apps/scrobbles/scrobblers.py +++ b/vrobbler/apps/scrobbles/scrobblers.py @@ -84,7 +84,11 @@ def mopidy_scrobble_media(post_data: dict, user_id: int) -> Scrobble: run_time_seconds=post_data.get("run_time", 900000), ) try: - album_id = Album.objects.filter(name=post_data.get("album", "")).first().id + album_id = ( + Album.objects.filter(name=post_data.get("album", "")) + .first() + .id + ) except Exception: pass @@ -104,14 +108,17 @@ def mopidy_scrobble_media(post_data: dict, user_id: int) -> Scrobble: user_id, source="Mopidy", playback_position_seconds=int( - post_data.get(MOPIDY_POST_KEYS.get("PLAYBACK_POSITION_TICKS"), 1) / 1000 + post_data.get(MOPIDY_POST_KEYS.get("PLAYBACK_POSITION_TICKS"), 1) + / 1000 ), status=post_data.get(MOPIDY_POST_KEYS.get("STATUS"), ""), log=log, ) -def jellyfin_scrobble_media(post_data: dict, user_id: int) -> Optional[Scrobble]: +def jellyfin_scrobble_media( + post_data: dict, user_id: int +) -> Optional[Scrobble]: media_type = Scrobble.MediaType.VIDEO if post_data.pop("ItemType", "") in JELLYFIN_AUDIO_ITEM_TYPES: media_type = Scrobble.MediaType.TRACK @@ -129,12 +136,13 @@ def jellyfin_scrobble_media(post_data: dict, user_id: int) -> Optional[Scrobble] ) return - timestamp = parse(post_data.get(JELLYFIN_POST_KEYS.get("TIMESTAMP"), "")).replace( - tzinfo=pytz.utc - ) + timestamp = parse( + post_data.get(JELLYFIN_POST_KEYS.get("TIMESTAMP"), "") + ).replace(tzinfo=pytz.utc) playback_position_seconds = int( - post_data.get(JELLYFIN_POST_KEYS.get("PLAYBACK_POSITION_TICKS"), 1) / 10000000 + post_data.get(JELLYFIN_POST_KEYS.get("PLAYBACK_POSITION_TICKS"), 1) + / 10000000 ) album_id = None if media_type == Scrobble.MediaType.VIDEO: @@ -145,10 +153,16 @@ def jellyfin_scrobble_media(post_data: dict, user_id: int) -> Optional[Scrobble] title=post_data.get("Name", ""), artist_name=post_data.get("Artist", ""), album_name=post_data.get("Album", ""), - run_time_seconds=convert_to_seconds(post_data.get("RunTime", 900000)), + run_time_seconds=convert_to_seconds( + post_data.get("RunTime", 900000) + ), ) try: - album_id = Album.objects.filter(name=post_data.get("Album", "")).first().id + album_id = ( + Album.objects.filter(name=post_data.get("Album", "")) + .first() + .id + ) except Exception: pass # A hack because we don't worry about updating music ... we either finish it or we don't @@ -314,7 +328,9 @@ def manual_scrobble_book( if not page: page = 1 - logger.info("[scrobblers] Book page included in scrobble, should update!") + logger.info( + "[scrobblers] Book page included in scrobble, should update!" + ) source = READCOMICSONLINE_URL.replace("https://", "") @@ -431,7 +447,9 @@ def email_scrobble_board_game( if not location.name: location.name = location_dict.get("name") update_fields.append("name") - geoloc = GeoLocation.objects.filter(title__icontains=location.name).first() + geoloc = GeoLocation.objects.filter( + title__icontains=location.name + ).first() if geoloc: location.geo_location = geoloc update_fields.append("geo_location") @@ -483,7 +501,9 @@ def email_scrobble_board_game( log_data.pop("expansion_ids") if play_dict.get("locationRefId", False): - log_data["location_id"] = locations[play_dict.get("locationRefId")].id + log_data["location_id"] = locations[ + play_dict.get("locationRefId") + ].id if play_dict.get("rounds", False): log_data["rounds"] = play_dict.get("rounds") if play_dict.get("board", False): @@ -506,7 +526,9 @@ def email_scrobble_board_game( timestamp = parse(play_dict.get("playDate")) if hour and minute: logger.info(f"Scrobble playDate has manual start time {timestamp}") - timestamp = timestamp.replace(hour=hour, minute=minute, second=second or 0) + timestamp = timestamp.replace( + hour=hour, minute=minute, second=second or 0 + ) logger.info(f"Update to {timestamp}") profile = UserProfile.objects.filter(user_id=user_id).first() @@ -601,7 +623,9 @@ def manual_scrobble_from_url( scrobbler. Otherwise, return nothing.""" if RecipeScraperService.is_recipe_url(url): - return scrobble_from_recipe_website(url, user_id, source=source, action=action) + return scrobble_from_recipe_website( + url, user_id, source=source, action=action + ) content_key = "" domain = extract_domain(url) @@ -804,7 +828,9 @@ def emacs_scrobble_update_task( if not scrobble.log.get('notes"'): scrobble.log["notes"] = [] if note.get("timestamp") not in existing_note_ts: - scrobble.log["notes"].append({note.get("timestamp"): note.get("content")}) + scrobble.log["notes"].append( + {note.get("timestamp"): note.get("content")} + ) notes_updated = True if notes_updated: @@ -830,7 +856,9 @@ def emacs_scrobble_task( user_context_list: list[str] = [], ) -> Scrobble | None: orgmode_id = task_data.get("source_id") - title = get_title_from_labels(task_data.get("labels", []), user_context_list) + title = get_title_from_labels( + task_data.get("labels", []), user_context_list + ) task = Task.find_or_create(title) @@ -953,7 +981,9 @@ def manual_scrobble_webpage( ): if RecipeScraperService.is_recipe_url(url): - return scrobble_from_recipe_website(url, user_id, source=source, action=action) + return scrobble_from_recipe_website( + url, user_id, source=source, action=action + ) webpage = WebPage.find_or_create({"url": url}) diff --git a/vrobbler/apps/videos/models.py b/vrobbler/apps/videos/models.py index e0901df..55cec78 100644 --- a/vrobbler/apps/videos/models.py +++ b/vrobbler/apps/videos/models.py @@ -75,6 +75,16 @@ class Channel(TimeStampedModel): url = self.cover_image_medium.url return url + @property + def safe_cover_image_url(self) -> str: + if self.cover_image: + try: + if self.cover_image.storage.exists(self.cover_image.name): + return self.cover_medium.url + except Exception: + pass + return "/static/images/not-found.jpg" + def scrobbles_for_user(self, user_id: int, include_playing=False): from scrobbles.models import Scrobble @@ -137,6 +147,16 @@ class Series(TimeStampedModel): url = self.cover_image_medium.url return url + @property + def safe_cover_image_url(self) -> str: + if self.cover_image: + try: + if self.cover_image.storage.exists(self.cover_image.name): + return self.cover_medium.url + except Exception: + pass + return "/static/images/not-found.jpg" + def save_image_from_url(self, url: str, force_update: bool = False): if not self.cover_image or (force_update and url): r = requests.get(url) @@ -158,17 +178,13 @@ class Series(TimeStampedModel): def last_scrobbled_episode(self, user_id: int) -> Optional["Video"]: episode = None - last_scrobble = self.scrobbles_for_user( - user_id, include_playing=True - ).first() + last_scrobble = self.scrobbles_for_user(user_id, include_playing=True).first() if last_scrobble: episode = last_scrobble.media_obj return episode def is_episode_playing(self, user_id: int) -> bool: - last_scrobble = self.scrobbles_for_user( - user_id, include_playing=True - ).first() + last_scrobble = self.scrobbles_for_user(user_id, include_playing=True).first() return not last_scrobble.played_to_completion def fix_metadata(self, force_update=False): @@ -321,6 +337,16 @@ class Video(ScrobblableMixin): url = self.cover_image_medium.url return url + @property + def safe_cover_image_url(self) -> str: + if self.cover_image: + try: + if self.cover_image.storage.exists(self.cover_image.name): + return self.cover_image_medium.url + except Exception: + pass + return "/static/images/not-found.jpg" + @property def strings(self) -> ScrobblableConstants: return ScrobblableConstants(verb="Watching", tags="movie_camera") @@ -333,9 +359,7 @@ class Video(ScrobblableMixin): self.cover_image.save(fname, ContentFile(r.content), save=True) @classmethod - def get_from_youtube_id( - cls, youtube_id: str, overwrite: bool = False - ) -> "Video": + def get_from_youtube_id(cls, youtube_id: str, overwrite: bool = False) -> "Video": video, created = cls.objects.get_or_create(youtube_id=youtube_id) if not created and not overwrite: return video @@ -355,9 +379,7 @@ class Video(ScrobblableMixin): return video @classmethod - def get_from_imdb_id( - cls, imdb_id: str, overwrite: bool = False - ) -> "Video": + def get_from_imdb_id(cls, imdb_id: str, overwrite: bool = False) -> "Video": video, created = cls.objects.get_or_create(imdb_id=imdb_id) if not created and not overwrite: return video diff --git a/vrobbler/templates/podcasts/podcast_detail.html b/vrobbler/templates/podcasts/podcast_detail.html index 8bacaaf..954f2a3 100644 --- a/vrobbler/templates/podcasts/podcast_detail.html +++ b/vrobbler/templates/podcasts/podcast_detail.html @@ -16,7 +16,7 @@
- +
{% if object.description %}

{{object.description|safe|linebreaks|truncatewords:160}}

{% endif %} diff --git a/vrobbler/templates/videos/channel_detail.html b/vrobbler/templates/videos/channel_detail.html index 78f9e05..95dba56 100644 --- a/vrobbler/templates/videos/channel_detail.html +++ b/vrobbler/templates/videos/channel_detail.html @@ -26,7 +26,7 @@
- +
{% if object.youtube_id %}

View on YouTube

{% endif %} diff --git a/vrobbler/templates/videos/series_detail.html b/vrobbler/templates/videos/series_detail.html index 36d6cb9..c5ca460 100644 --- a/vrobbler/templates/videos/series_detail.html +++ b/vrobbler/templates/videos/series_detail.html @@ -27,7 +27,7 @@
{% if object.imdb_rating %}
{{object.imdb_rating}}
{% endif %} - + {% if next_episode_id %}
{% csrf_token %} diff --git a/vrobbler/templates/videos/video_detail.html b/vrobbler/templates/videos/video_detail.html index 13bdad4..5614838 100644 --- a/vrobbler/templates/videos/video_detail.html +++ b/vrobbler/templates/videos/video_detail.html @@ -58,7 +58,7 @@ dd {
{% if object.imdb_rating %}
{{object.imdb_rating}}
{% endif %} {% if object.tmdb_rating %}
{{object.tmdb_rating}}
{% endif %} - +