[templates] Try using defensive checks for missing media
This commit is contained in:
@ -39,9 +39,7 @@ class Producer(TimeStampedModel):
|
|||||||
class Podcast(TimeStampedModel):
|
class Podcast(TimeStampedModel):
|
||||||
name = models.CharField(max_length=255)
|
name = models.CharField(max_length=255)
|
||||||
uuid = models.UUIDField(default=uuid4, editable=False, **BNULL)
|
uuid = models.UUIDField(default=uuid4, editable=False, **BNULL)
|
||||||
producer = models.ForeignKey(
|
producer = models.ForeignKey(Producer, on_delete=models.DO_NOTHING, **BNULL)
|
||||||
Producer, on_delete=models.DO_NOTHING, **BNULL
|
|
||||||
)
|
|
||||||
podcastindex_id = models.CharField(max_length=100, **BNULL)
|
podcastindex_id = models.CharField(max_length=100, **BNULL)
|
||||||
owner = models.CharField(max_length=150, *BNULL)
|
owner = models.CharField(max_length=150, *BNULL)
|
||||||
description = models.TextField(**BNULL)
|
description = models.TextField(**BNULL)
|
||||||
@ -60,6 +58,16 @@ class Podcast(TimeStampedModel):
|
|||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse("podcasts:podcast_detail", kwargs={"slug": self.uuid})
|
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):
|
def scrobbles(self, user):
|
||||||
Scrobble = apps.get_model("scrobbles", "Scrobble")
|
Scrobble = apps.get_model("scrobbles", "Scrobble")
|
||||||
return Scrobble.objects.filter(
|
return Scrobble.objects.filter(
|
||||||
@ -113,9 +121,7 @@ class PodcastEpisode(ScrobblableMixin):
|
|||||||
mopidy_uri = models.CharField(max_length=255, **BNULL)
|
mopidy_uri = models.CharField(max_length=255, **BNULL)
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse(
|
return reverse("podcasts:podcast_detail", kwargs={"slug": self.podcast.uuid})
|
||||||
"podcasts:podcast_detail", kwargs={"slug": self.podcast.uuid}
|
|
||||||
)
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.title}"
|
return f"{self.title}"
|
||||||
|
|||||||
@ -84,7 +84,11 @@ def mopidy_scrobble_media(post_data: dict, user_id: int) -> Scrobble:
|
|||||||
run_time_seconds=post_data.get("run_time", 900000),
|
run_time_seconds=post_data.get("run_time", 900000),
|
||||||
)
|
)
|
||||||
try:
|
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:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -104,14 +108,17 @@ def mopidy_scrobble_media(post_data: dict, user_id: int) -> Scrobble:
|
|||||||
user_id,
|
user_id,
|
||||||
source="Mopidy",
|
source="Mopidy",
|
||||||
playback_position_seconds=int(
|
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"), ""),
|
status=post_data.get(MOPIDY_POST_KEYS.get("STATUS"), ""),
|
||||||
log=log,
|
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
|
media_type = Scrobble.MediaType.VIDEO
|
||||||
if post_data.pop("ItemType", "") in JELLYFIN_AUDIO_ITEM_TYPES:
|
if post_data.pop("ItemType", "") in JELLYFIN_AUDIO_ITEM_TYPES:
|
||||||
media_type = Scrobble.MediaType.TRACK
|
media_type = Scrobble.MediaType.TRACK
|
||||||
@ -129,12 +136,13 @@ def jellyfin_scrobble_media(post_data: dict, user_id: int) -> Optional[Scrobble]
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
timestamp = parse(post_data.get(JELLYFIN_POST_KEYS.get("TIMESTAMP"), "")).replace(
|
timestamp = parse(
|
||||||
tzinfo=pytz.utc
|
post_data.get(JELLYFIN_POST_KEYS.get("TIMESTAMP"), "")
|
||||||
)
|
).replace(tzinfo=pytz.utc)
|
||||||
|
|
||||||
playback_position_seconds = int(
|
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
|
album_id = None
|
||||||
if media_type == Scrobble.MediaType.VIDEO:
|
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", ""),
|
title=post_data.get("Name", ""),
|
||||||
artist_name=post_data.get("Artist", ""),
|
artist_name=post_data.get("Artist", ""),
|
||||||
album_name=post_data.get("Album", ""),
|
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:
|
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:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
# A hack because we don't worry about updating music ... we either finish it or we don't
|
# 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:
|
if not page:
|
||||||
page = 1
|
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://", "")
|
source = READCOMICSONLINE_URL.replace("https://", "")
|
||||||
|
|
||||||
@ -431,7 +447,9 @@ def email_scrobble_board_game(
|
|||||||
if not location.name:
|
if not location.name:
|
||||||
location.name = location_dict.get("name")
|
location.name = location_dict.get("name")
|
||||||
update_fields.append("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:
|
if geoloc:
|
||||||
location.geo_location = geoloc
|
location.geo_location = geoloc
|
||||||
update_fields.append("geo_location")
|
update_fields.append("geo_location")
|
||||||
@ -483,7 +501,9 @@ def email_scrobble_board_game(
|
|||||||
log_data.pop("expansion_ids")
|
log_data.pop("expansion_ids")
|
||||||
|
|
||||||
if play_dict.get("locationRefId", False):
|
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):
|
if play_dict.get("rounds", False):
|
||||||
log_data["rounds"] = play_dict.get("rounds")
|
log_data["rounds"] = play_dict.get("rounds")
|
||||||
if play_dict.get("board", False):
|
if play_dict.get("board", False):
|
||||||
@ -506,7 +526,9 @@ def email_scrobble_board_game(
|
|||||||
timestamp = parse(play_dict.get("playDate"))
|
timestamp = parse(play_dict.get("playDate"))
|
||||||
if hour and minute:
|
if hour and minute:
|
||||||
logger.info(f"Scrobble playDate has manual start time {timestamp}")
|
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}")
|
logger.info(f"Update to {timestamp}")
|
||||||
|
|
||||||
profile = UserProfile.objects.filter(user_id=user_id).first()
|
profile = UserProfile.objects.filter(user_id=user_id).first()
|
||||||
@ -601,7 +623,9 @@ def manual_scrobble_from_url(
|
|||||||
scrobbler. Otherwise, return nothing."""
|
scrobbler. Otherwise, return nothing."""
|
||||||
|
|
||||||
if RecipeScraperService.is_recipe_url(url):
|
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 = ""
|
content_key = ""
|
||||||
domain = extract_domain(url)
|
domain = extract_domain(url)
|
||||||
@ -804,7 +828,9 @@ def emacs_scrobble_update_task(
|
|||||||
if not scrobble.log.get('notes"'):
|
if not scrobble.log.get('notes"'):
|
||||||
scrobble.log["notes"] = []
|
scrobble.log["notes"] = []
|
||||||
if note.get("timestamp") not in existing_note_ts:
|
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
|
notes_updated = True
|
||||||
|
|
||||||
if notes_updated:
|
if notes_updated:
|
||||||
@ -830,7 +856,9 @@ def emacs_scrobble_task(
|
|||||||
user_context_list: list[str] = [],
|
user_context_list: list[str] = [],
|
||||||
) -> Scrobble | None:
|
) -> Scrobble | None:
|
||||||
orgmode_id = task_data.get("source_id")
|
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)
|
task = Task.find_or_create(title)
|
||||||
|
|
||||||
@ -953,7 +981,9 @@ def manual_scrobble_webpage(
|
|||||||
):
|
):
|
||||||
|
|
||||||
if RecipeScraperService.is_recipe_url(url):
|
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})
|
webpage = WebPage.find_or_create({"url": url})
|
||||||
|
|
||||||
|
|||||||
@ -75,6 +75,16 @@ class Channel(TimeStampedModel):
|
|||||||
url = self.cover_image_medium.url
|
url = self.cover_image_medium.url
|
||||||
return 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):
|
def scrobbles_for_user(self, user_id: int, include_playing=False):
|
||||||
from scrobbles.models import Scrobble
|
from scrobbles.models import Scrobble
|
||||||
|
|
||||||
@ -137,6 +147,16 @@ class Series(TimeStampedModel):
|
|||||||
url = self.cover_image_medium.url
|
url = self.cover_image_medium.url
|
||||||
return 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):
|
def save_image_from_url(self, url: str, force_update: bool = False):
|
||||||
if not self.cover_image or (force_update and url):
|
if not self.cover_image or (force_update and url):
|
||||||
r = requests.get(url)
|
r = requests.get(url)
|
||||||
@ -158,17 +178,13 @@ class Series(TimeStampedModel):
|
|||||||
|
|
||||||
def last_scrobbled_episode(self, user_id: int) -> Optional["Video"]:
|
def last_scrobbled_episode(self, user_id: int) -> Optional["Video"]:
|
||||||
episode = None
|
episode = None
|
||||||
last_scrobble = self.scrobbles_for_user(
|
last_scrobble = self.scrobbles_for_user(user_id, include_playing=True).first()
|
||||||
user_id, include_playing=True
|
|
||||||
).first()
|
|
||||||
if last_scrobble:
|
if last_scrobble:
|
||||||
episode = last_scrobble.media_obj
|
episode = last_scrobble.media_obj
|
||||||
return episode
|
return episode
|
||||||
|
|
||||||
def is_episode_playing(self, user_id: int) -> bool:
|
def is_episode_playing(self, user_id: int) -> bool:
|
||||||
last_scrobble = self.scrobbles_for_user(
|
last_scrobble = self.scrobbles_for_user(user_id, include_playing=True).first()
|
||||||
user_id, include_playing=True
|
|
||||||
).first()
|
|
||||||
return not last_scrobble.played_to_completion
|
return not last_scrobble.played_to_completion
|
||||||
|
|
||||||
def fix_metadata(self, force_update=False):
|
def fix_metadata(self, force_update=False):
|
||||||
@ -321,6 +337,16 @@ class Video(ScrobblableMixin):
|
|||||||
url = self.cover_image_medium.url
|
url = self.cover_image_medium.url
|
||||||
return 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
|
@property
|
||||||
def strings(self) -> ScrobblableConstants:
|
def strings(self) -> ScrobblableConstants:
|
||||||
return ScrobblableConstants(verb="Watching", tags="movie_camera")
|
return ScrobblableConstants(verb="Watching", tags="movie_camera")
|
||||||
@ -333,9 +359,7 @@ class Video(ScrobblableMixin):
|
|||||||
self.cover_image.save(fname, ContentFile(r.content), save=True)
|
self.cover_image.save(fname, ContentFile(r.content), save=True)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_from_youtube_id(
|
def get_from_youtube_id(cls, youtube_id: str, overwrite: bool = False) -> "Video":
|
||||||
cls, youtube_id: str, overwrite: bool = False
|
|
||||||
) -> "Video":
|
|
||||||
video, created = cls.objects.get_or_create(youtube_id=youtube_id)
|
video, created = cls.objects.get_or_create(youtube_id=youtube_id)
|
||||||
if not created and not overwrite:
|
if not created and not overwrite:
|
||||||
return video
|
return video
|
||||||
@ -355,9 +379,7 @@ class Video(ScrobblableMixin):
|
|||||||
return video
|
return video
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_from_imdb_id(
|
def get_from_imdb_id(cls, imdb_id: str, overwrite: bool = False) -> "Video":
|
||||||
cls, imdb_id: str, overwrite: bool = False
|
|
||||||
) -> "Video":
|
|
||||||
video, created = cls.objects.get_or_create(imdb_id=imdb_id)
|
video, created = cls.objects.get_or_create(imdb_id=imdb_id)
|
||||||
if not created and not overwrite:
|
if not created and not overwrite:
|
||||||
return video
|
return video
|
||||||
|
|||||||
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
<div class="row header">
|
<div class="row header">
|
||||||
<div class="cover image-wrapper">
|
<div class="cover image-wrapper">
|
||||||
<img src="{% if object.cover_image %}{{object.cover_image.url}}{% else %}{% static 'images/no-video-cover.jpg' %}{% endif %}" width="400px" />
|
<img src="{{ object.safe_cover_image_url }}" width="400px" />
|
||||||
</div>
|
</div>
|
||||||
<div class="summary">
|
<div class="summary">
|
||||||
{% if object.description %}<p>{{object.description|safe|linebreaks|truncatewords:160}}</p>{% endif %}
|
{% if object.description %}<p>{{object.description|safe|linebreaks|truncatewords:160}}</p>{% endif %}
|
||||||
|
|||||||
@ -26,7 +26,7 @@
|
|||||||
|
|
||||||
<div class="row header">
|
<div class="row header">
|
||||||
<div class="cover image-wrapper">
|
<div class="cover image-wrapper">
|
||||||
<img src="{% if object.cover_image %}{{object.cover_image.url}}{% else %}{% static 'images/no-video-cover.jpg' %}{% endif %}" width="400px" />
|
<img src="{{ object.safe_cover_image_url }}" width="400px" />
|
||||||
</div>
|
</div>
|
||||||
<div class="summary">
|
<div class="summary">
|
||||||
{% if object.youtube_id %}<p><a href="{{object.youtube_url}}" target="_blank">View on YouTube</a></p>{% endif %}
|
{% if object.youtube_id %}<p><a href="{{object.youtube_url}}" target="_blank">View on YouTube</a></p>{% endif %}
|
||||||
|
|||||||
@ -27,7 +27,7 @@
|
|||||||
<div class="row header">
|
<div class="row header">
|
||||||
<div class="cover image-wrapper">
|
<div class="cover image-wrapper">
|
||||||
{% if object.imdb_rating %}<div class="caption">{{object.imdb_rating}}</div>{% endif %}
|
{% if object.imdb_rating %}<div class="caption">{{object.imdb_rating}}</div>{% endif %}
|
||||||
<img src="{% if object.cover_image %}{{object.cover_image.url}}{% else %}{% static 'images/no-video-cover.jpg' %}{% endif %}" width="400px" />
|
<img src="{{ object.safe_cover_image_url }}" width="400px" />
|
||||||
{% if next_episode_id %}
|
{% if next_episode_id %}
|
||||||
<form id="scrobble-form" action="{% url 'scrobbles:lookup-manual-scrobble' %}" method="post">
|
<form id="scrobble-form" action="{% url 'scrobbles:lookup-manual-scrobble' %}" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|||||||
@ -58,7 +58,7 @@ dd {
|
|||||||
<div class="cover image-wrapper">
|
<div class="cover image-wrapper">
|
||||||
{% if object.imdb_rating %}<div class="caption">{{object.imdb_rating}}</div>{% endif %}
|
{% if object.imdb_rating %}<div class="caption">{{object.imdb_rating}}</div>{% endif %}
|
||||||
{% if object.tmdb_rating %}<div class="caption">{{object.tmdb_rating}}</div>{% endif %}
|
{% if object.tmdb_rating %}<div class="caption">{{object.tmdb_rating}}</div>{% endif %}
|
||||||
<img src="{% if object.cover_image %}{{object.cover_image.url}}{% else %}{% static 'images/no-video-cover.jpg' %}{% endif %}" width="400px" />
|
<img src="{{ object.safe_cover_image_url }}" width="400px" />
|
||||||
<div class="caption-footer">{{object.year}}{% if object.tv_series %} | <b>S</b>{{object.season_number}} <b>E</b>{{object.episode_number}}{% endif %}</div>
|
<div class="caption-footer">{{object.year}}{% if object.tv_series %} | <b>S</b>{{object.season_number}} <b>E</b>{{object.episode_number}}{% endif %}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="summary">
|
<div class="summary">
|
||||||
|
|||||||
Reference in New Issue
Block a user