Merge pull request #1168 from connorjburton/movie-cast

Add basic view of 10 cast by billing order for movies
This commit is contained in:
Xila Cai
2026-02-15 10:58:22 +01:00
committed by GitHub
3 changed files with 146 additions and 92 deletions

View File

@@ -151,7 +151,7 @@ def movie(media_id):
url = f"{base_url}/movie/{media_id}"
params = {
**base_params,
"append_to_response": "recommendations,external_ids",
"append_to_response": "recommendations,external_ids,credits",
}
try:
@@ -188,6 +188,19 @@ def movie(media_id):
item for item in recommended_items if item["id"] not in collection_ids
]
cast = response.get("credits", {}).get("cast", [])
filtered_cast = [
{
"id": member.get("id"),
"name": member.get("name"),
"character": member.get("character"),
"image": get_image_url(member.get("profile_path"))
if member.get("profile_path")
else None,
}
for member in cast[:10]
]
data = {
"media_id": media_id,
"source": Sources.TMDB.value,
@@ -209,6 +222,7 @@ def movie(media_id):
"country": get_country(response["production_countries"]),
"languages": get_languages(response["spoken_languages"]),
},
"cast": filtered_cast,
"related": {
collection_response.get("name", "collection"): collection_items,
"recommendations": get_related(

View File

@@ -0,0 +1,28 @@
{% load app_tags %}
<div class="bg-[#2a2f35] rounded-lg overflow-hidden shadow-lg relative group">
<div class="relative">
<a href="https://www.themoviedb.org/person/{{ cast.id }}"
target="_blank">
<img alt="{{ cast.name }}"
class="lazyload w-full aspect-2/3 bg-[#3e454d] {% if cast.image %}object-cover{% endif %}"
data-src="{{ cast.image }}"
src="{{ IMG_NONE }}">
</a>
<div class="absolute inset-0 bg-black/40 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity duration-200">
<a href="https://www.themoviedb.org/person/{{ cast.id }}"
target="_blank"
class="p-2.5 bg-indigo-600 text-white rounded-full hover:bg-indigo-500 hover:scale-110 transition-all duration-200">
{% include "app/icons/external-link.svg" with classes="w-5 h-5" %}
</a>
</div>
</div>
<div class="p-3">
<div class="text-sm font-semibold text-white line-clamp-1"
title="{{ cast.name }}">{{ cast.name }}</div>
<div class="text-xs text-gray-400 line-clamp-1 mt-1"
title="{{ cast.character }}">{{ cast.character }}</div>
</div>
</div>

View File

@@ -469,9 +469,21 @@
</div>
</div>
{# Related Media #}
{% if media.related %}
<div class="w-full md:w-3/4">
<div class="w-full md:w-3/4">
{% if media.cast %}
<section class="mb-8">
<h2 class="text-xl font-bold mb-4">Cast</h2>
<div class="grid grid-cols-[repeat(auto-fill,minmax(150px,1fr))] gap-4">
{% for cast in media.cast %}
{% include "app/components/cast_card.html" with cast=cast only %}
{% endfor %}
</div>
</section>
{% endif %}
{# Related Media #}
{% if media.related %}
{% for name, related_items in media.related.items %}
{% if related_items %}
<section class="{% if not forloop.last %}mb-8{% endif %}">
@@ -495,109 +507,109 @@
</section>
{% endif %}
{% endfor %}
</div>
{% endif %}
{# Episodes List #}
{% if media.episodes %}
<div class="w-full">
<h2 class="text-xl font-bold mb-4">Episodes</h2>
<div class="space-y-6">
{% for episode in media.episodes %}
<div class="bg-[#2a2f35] rounded-lg overflow-hidden shadow-lg">
<div class="flex flex-col md:flex-row">
<img src="{{ IMG_NONE }}"
alt="E{{ episode.episode_number }}"
data-src="{{ episode.image }}"
class="lazyload md:w-64 md:h-40 shrink-0 {% if episode.image != IMG_NONE %}object-cover{% endif %}">
<div class="py-3 flex-1 flex flex-col">
<div class="flex-1">
<div class="flex justify-between items-start mb-2 space-x-2 px-4">
<div>
<h2 class="text-xl font-semibold mb-1 line-clamp-1">{{ episode.title }}</h2>
<p class="text-sm text-gray-400">
Episode {{ episode.episode_number }} • {{ episode.air_date|default_if_none:"Unknown air date" }}
{% if episode.runtime %}• {{ episode.runtime }}{% endif %}
</p>
</div>
<div class="flex space-x-2">
{# Track Episode #}
<div x-data="{ trackOpen: false }">
<button @click="trackOpen = true"
title="Track Episode"
class="p-2 bg-indigo-600 hover:bg-indigo-700 text-white rounded-full transition duration-300 cursor-pointer">
{% if episode.history %}
{% include "app/icons/eye.svg" with classes="w-4 h-4" %}
{% else %}
{% include "app/icons/eye-closed.svg" with classes="w-4 h-4" %}
{% endif %}
</button>
{% include "app/components/fill_track_episode.html" with request=request media=media episode=episode episode_title=episode.title csrf_token=csrf_token TRACK_TIME=TRACK_TIME only %}
{% endif %}
{# Episodes List #}
{% if media.episodes %}
<div class="w-full">
<h2 class="text-xl font-bold mb-4">Episodes</h2>
<div class="space-y-6">
{% for episode in media.episodes %}
<div class="bg-[#2a2f35] rounded-lg overflow-hidden shadow-lg">
<div class="flex flex-col md:flex-row">
<img src="{{ IMG_NONE }}"
alt="E{{ episode.episode_number }}"
data-src="{{ episode.image }}"
class="lazyload md:w-64 md:h-40 shrink-0 {% if episode.image != IMG_NONE %}object-cover{% endif %}">
<div class="py-3 flex-1 flex flex-col">
<div class="flex-1">
<div class="flex justify-between items-start mb-2 space-x-2 px-4">
<div>
<h2 class="text-xl font-semibold mb-1 line-clamp-1">{{ episode.title }}</h2>
<p class="text-sm text-gray-400">
Episode {{ episode.episode_number }} • {{ episode.air_date|default_if_none:"Unknown air date" }}
{% if episode.runtime %}• {{ episode.runtime }}{% endif %}
</p>
</div>
<div class="flex space-x-2">
{# Track Episode #}
<div x-data="{ trackOpen: false }">
<button @click="trackOpen = true"
title="Track Episode"
class="p-2 bg-indigo-600 hover:bg-indigo-700 text-white rounded-full transition duration-300 cursor-pointer">
{% if episode.history %}
{% include "app/icons/eye.svg" with classes="w-4 h-4" %}
{% else %}
{% include "app/icons/eye-closed.svg" with classes="w-4 h-4" %}
{% endif %}
</button>
{# Lists #}
<div x-data="{ listsOpen: false }">
<button class="p-2 bg-emerald-600 hover:bg-emerald-700 text-white rounded-full transition duration-300 cursor-pointer"
title="Add to custom lists"
hx-get="{% media_view_url 'lists_modal' episode %}"
hx-vals='{"return_url": "{{ request.get_full_path|urlencode }}"}'
hx-target="#{% component_id 'lists' episode %}"
hx-trigger="click once"
@click="listsOpen = true">
{% include "app/icons/list-add.svg" with classes="w-4 h-4" %}
</button>
{% include "app/components/fill_track_episode.html" with request=request media=media episode=episode episode_title=episode.title csrf_token=csrf_token TRACK_TIME=TRACK_TIME only %}
<div x-show="listsOpen"
@keydown.escape.window="listsOpen = false"
class="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
<div class="w-96 max-h-[90vh] px-4 md:px-0 relative z-60"
@click.outside="listsOpen = false">
<div id="{% component_id 'lists' episode %}"></div>
</div>
{# Lists #}
<div x-data="{ listsOpen: false }">
<button class="p-2 bg-emerald-600 hover:bg-emerald-700 text-white rounded-full transition duration-300 cursor-pointer"
title="Add to custom lists"
hx-get="{% media_view_url 'lists_modal' episode %}"
hx-vals='{"return_url": "{{ request.get_full_path|urlencode }}"}'
hx-target="#{% component_id 'lists' episode %}"
hx-trigger="click once"
@click="listsOpen = true">
{% include "app/icons/list-add.svg" with classes="w-4 h-4" %}
</button>
<div x-show="listsOpen"
@keydown.escape.window="listsOpen = false"
class="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
<div class="w-96 max-h-[90vh] px-4 md:px-0 relative z-60"
@click.outside="listsOpen = false">
<div id="{% component_id 'lists' episode %}"></div>
</div>
</div>
</div>
</div>
{# History #}
<div x-data="{ historyOpen: false }">
<button class="p-2 bg-amber-600 hover:bg-amber-700 text-white rounded-full transition duration-300 cursor-pointer"
title="View your activity history"
hx-get="{% media_view_url 'history_modal' episode %}"
hx-vals='{"return_url": "{{ request.get_full_path|urlencode }}"}'
hx-target="#{% component_id 'history' episode %}"
hx-trigger="click once"
@click="historyOpen = true">
{% include "app/icons/history.svg" with classes="w-4 h-4" %}
</button>
<div x-show="historyOpen"
@keydown.escape.window="historyOpen = false"
class="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
<div class="w-96 max-h-[90vh] px-4 md:px-0 relative z-60"
@click.outside="historyOpen = false">
<div id="{% component_id 'history' episode %}"></div>
{# History #}
<div x-data="{ historyOpen: false }">
<button class="p-2 bg-amber-600 hover:bg-amber-700 text-white rounded-full transition duration-300 cursor-pointer"
title="View your activity history"
hx-get="{% media_view_url 'history_modal' episode %}"
hx-vals='{"return_url": "{{ request.get_full_path|urlencode }}"}'
hx-target="#{% component_id 'history' episode %}"
hx-trigger="click once"
@click="historyOpen = true">
{% include "app/icons/history.svg" with classes="w-4 h-4" %}
</button>
<div x-show="historyOpen"
@keydown.escape.window="historyOpen = false"
class="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
<div class="w-96 max-h-[90vh] px-4 md:px-0 relative z-60"
@click.outside="historyOpen = false">
<div id="{% component_id 'history' episode %}"></div>
</div>
</div>
</div>
</div>
</div>
<div class="{% if episode.history %}md:h-[calc(2*1.5rem)]{% else %}md:h-[calc(3*1.5rem)]{% endif %} md:overflow-y-auto px-4">
<p class="text-sm text-gray-300 leading-relaxed text-pretty">{{ episode.overview }}</p>
</div>
</div>
<div class="{% if episode.history %}md:h-[calc(2*1.5rem)]{% else %}md:h-[calc(3*1.5rem)]{% endif %} md:overflow-y-auto px-4">
<p class="text-sm text-gray-300 leading-relaxed text-pretty">{{ episode.overview }}</p>
</div>
{% if episode.history %}
<p class="text-xs text-gray-400 mt-2 px-4">
Last watched: {{ episode.history.0.end_date|datetime_format:user }}
{% if not episode.history.0.end_date %}No date provided{% endif %}
{% if episode.history|length > 1 %}• Watched {{ episode.history|length }} times{% endif %}
</p>
{% endif %}
</div>
{% if episode.history %}
<p class="text-xs text-gray-400 mt-2 px-4">
Last watched: {{ episode.history.0.end_date|datetime_format:user }}
{% if not episode.history.0.end_date %}No date provided{% endif %}
{% if episode.history|length > 1 %}• Watched {{ episode.history|length }} times{% endif %}
</p>
{% endif %}
</div>
</div>
</div>
{% endfor %}
{% endfor %}
</div>
</div>
</div>
{% endif %}
{% endif %}
</div>
</div>
{% endblock content %}