diff options
| author | Bobby <[email protected]> | 2024-09-04 03:47:53 -0400 |
|---|---|---|
| committer | Bobby <[email protected]> | 2024-09-04 03:47:53 -0400 |
| commit | f7b777fbca7569a98cb51aa983de9cf82c070ac9 (patch) | |
| tree | 94ac7a3b862d7a594e34e12a798042f01f14e01a | |
| parent | b73b047452ba47141a96cf000333fc9a7211aa91 (diff) | |
| download | yugen-f7b777fbca7569a98cb51aa983de9cf82c070ac9.tar.xz yugen-f7b777fbca7569a98cb51aa983de9cf82c070ac9.zip | |
seasons and mal and zoro id stream
| -rw-r--r-- | static/css/input.css | 2 | ||||
| -rw-r--r-- | static/css/main.css | 86 | ||||
| -rw-r--r-- | templates/home/index.html | 8 | ||||
| -rw-r--r-- | templates/partials/colors.html | 24 | ||||
| -rw-r--r-- | templates/watch/watch.html | 61 | ||||
| -rw-r--r-- | watch/urls.py | 8 | ||||
| -rw-r--r-- | watch/views.py | 239 |
7 files changed, 370 insertions, 58 deletions
diff --git a/static/css/input.css b/static/css/input.css index 3c6acc2..d40e436 100644 --- a/static/css/input.css +++ b/static/css/input.css @@ -46,7 +46,7 @@ main { transition: transform 0.1s ease-in-out; } -.historySwiper .swiper-slide { +.historySwiper .swiper-slide, .seasonSwiper .swiper-slide { height: 12rem !important; /* equivalent to h-48 */ width: auto !important; aspect-ratio: 16 / 9 !important; diff --git a/static/css/main.css b/static/css/main.css index d85407a..33882fe 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -858,10 +858,6 @@ video { height: 100%; } -.h-64 { - height: 16rem; -} - .max-h-24 { max-height: 6rem; } @@ -939,18 +935,10 @@ video { width: max-content; } -.w-52 { - width: 13rem; -} - .min-w-32 { min-width: 8rem; } -.min-w-56 { - min-width: 14rem; -} - .max-w-4xl { max-width: 56rem; } @@ -980,10 +968,6 @@ video { max-width: max-content; } -.max-w-56 { - max-width: 14rem; -} - .flex-1 { flex: 1 1 0%; } @@ -1126,10 +1110,6 @@ video { overflow: hidden; } -.overflow-scroll { - overflow: scroll; -} - .overflow-x-auto { overflow-x: auto; } @@ -1184,10 +1164,44 @@ video { border-width: 1px; } +.border-4 { + border-width: 4px; +} + .border-b { border-bottom-width: 1px; } +.border-amber-600 { + --tw-border-opacity: 1; + border-color: rgb(217 119 6 / var(--tw-border-opacity)); +} + +.border-blue-600 { + --tw-border-opacity: 1; + border-color: rgb(37 99 235 / var(--tw-border-opacity)); +} + +.border-cyan-600 { + --tw-border-opacity: 1; + border-color: rgb(8 145 178 / var(--tw-border-opacity)); +} + +.border-green-600 { + --tw-border-opacity: 1; + border-color: rgb(22 163 74 / var(--tw-border-opacity)); +} + +.border-indigo-600 { + --tw-border-opacity: 1; + border-color: rgb(79 70 229 / var(--tw-border-opacity)); +} + +.border-lime-600 { + --tw-border-opacity: 1; + border-color: rgb(101 163 13 / var(--tw-border-opacity)); +} + .border-neutral-700 { --tw-border-opacity: 1; border-color: rgb(64 64 64 / var(--tw-border-opacity)); @@ -1198,11 +1212,41 @@ video { border-color: rgb(38 38 38 / var(--tw-border-opacity)); } +.border-orange-600 { + --tw-border-opacity: 1; + border-color: rgb(234 88 12 / var(--tw-border-opacity)); +} + +.border-pink-600 { + --tw-border-opacity: 1; + border-color: rgb(219 39 119 / var(--tw-border-opacity)); +} + +.border-purple-600 { + --tw-border-opacity: 1; + border-color: rgb(147 51 234 / var(--tw-border-opacity)); +} + +.border-red-600 { + --tw-border-opacity: 1; + border-color: rgb(220 38 38 / var(--tw-border-opacity)); +} + +.border-teal-600 { + --tw-border-opacity: 1; + border-color: rgb(13 148 136 / var(--tw-border-opacity)); +} + .border-white { --tw-border-opacity: 1; border-color: rgb(255 255 255 / var(--tw-border-opacity)); } +.border-yellow-600 { + --tw-border-opacity: 1; + border-color: rgb(202 138 4 / var(--tw-border-opacity)); +} + .border-opacity-10 { --tw-border-opacity: 0.1; } @@ -2046,7 +2090,7 @@ main { transition: transform 0.1s ease-in-out; } -.historySwiper .swiper-slide { +.historySwiper .swiper-slide, .seasonSwiper .swiper-slide { height: 12rem !important; /* equivalent to h-48 */ width: auto !important; diff --git a/templates/home/index.html b/templates/home/index.html index e9d9093..275a36b 100644 --- a/templates/home/index.html +++ b/templates/home/index.html @@ -1,8 +1,10 @@ -{% extends "partials/base.html" %} {% block css %} +{% extends "partials/base.html" %} {% load custom_filters %} +{% block css %} <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css"/> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css"/> -{% endblock css %} {% block content %} +{% endblock css %} +{% block content %} <!-- Swiper: Trending Anime --> <section class="swiper bigSwiper rounded mt-2"> <div class="swiper-wrapper"> @@ -108,7 +110,7 @@ <div class="swiper-wrapper"> {% for history in user_history_data %} <a href="{% url "watch:watch_episode" history.anime.id history.episode.number %}" class="group rounded-lg aspect-video h-48 relative flex-shrink-0 overflow-hidden swiper-slide"> - <div class="absolute inset-0 bg-center bg-cover transition-transform group-hover:scale-110" style="background-image: url('{% if history.episode.image %}{{ history.episode.image }}'){% else %}{{ history.anime.cover }}{% endif %}"></div> + <div class="absolute inset-0 bg-center bg-cover transition-transform group-hover:scale-110" style="background-image: url('{% if history.episode.image %}{{ history.episode.image }}{% else %}{{ history.anime.cover }}{% endif %}')"></div> <div class="absolute inset-0 bg-{{ user.preferences.accent_colour }}-600 opacity-0 group-hover:opacity-30 transition-opacity"></div> <div class="absolute inset-0" style="background: linear-gradient(45deg, rgb(8, 8, 8) 15%, transparent 60%), linear-gradient(0deg, rgb(8, 8, 8) 0%, transparent 60%);"></div> <div class="flex flex-col justify-end h-full p-2 relative z-10"> diff --git a/templates/partials/colors.html b/templates/partials/colors.html index 20330b2..c4c18d5 100644 --- a/templates/partials/colors.html +++ b/templates/partials/colors.html @@ -15,18 +15,18 @@ <div class="bg-amber-400 hover:bg-amber-400 text-amber-400"></div> <!-- 600 Colors + Checkboxes--> - <div class="bg-purple-600 hover:bg-purple-600 text-purple-600 peer-checked:bg-purple-600"></div> - <div class="bg-blue-600 hover:bg-blue-600 text-blue-600 peer-checked:bg-blue-600"></div> - <div class="bg-green-600 hover:bg-green-600 text-green-600 peer-checked:bg-green-600"></div> - <div class="bg-yellow-600 hover:bg-yellow-600 text-yellow-600 peer-checked:bg-yellow-600"></div> - <div class="bg-red-600 hover:bg-red-600 text-red-600 peer-checked:bg-red-600"></div> - <div class="bg-pink-600 hover:bg-pink-600 text-pink-600 peer-checked:bg-pink-600"></div> - <div class="bg-indigo-600 hover:bg-indigo-600 text-indigo-600 peer-checked:bg-indigo-600"></div> - <div class="bg-cyan-600 hover:bg-cyan-600 text-cyan-600 peer-checked:bg-cyan-600"></div> - <div class="bg-orange-600 hover:bg-orange-600 text-orange-600 peer-checked:bg-orange-600"></div> - <div class="bg-teal-600 hover:bg-teal-600 text-teal-600 peer-checked:bg-teal-600"></div> - <div class="bg-lime-600 hover:bg-lime-600 text-lime-600 peer-checked:bg-lime-600"></div> - <div class="bg-amber-600 hover:bg-amber-600 text-amber-600 peer-checked:bg-amber-600"></div> + <div class="bg-purple-600 hover:bg-purple-600 text-purple-600 peer-checked:bg-purple-600 border-purple-600"></div> + <div class="bg-blue-600 hover:bg-blue-600 text-blue-600 peer-checked:bg-blue-600 border-blue-600"></div> + <div class="bg-green-600 hover:bg-green-600 text-green-600 peer-checked:bg-green-600 border-green-600"></div> + <div class="bg-yellow-600 hover:bg-yellow-600 text-yellow-600 peer-checked:bg-yellow-600 border-yellow-600"></div> + <div class="bg-red-600 hover:bg-red-600 text-red-600 peer-checked:bg-red-600 border-red-600"></div> + <div class="bg-pink-600 hover:bg-pink-600 text-pink-600 peer-checked:bg-pink-600 border-pink-600"></div> + <div class="bg-indigo-600 hover:bg-indigo-600 text-indigo-600 peer-checked:bg-indigo-600 border-indigo-600"></div> + <div class="bg-cyan-600 hover:bg-cyan-600 text-cyan-600 peer-checked:bg-cyan-600 border-cyan-600"></div> + <div class="bg-orange-600 hover:bg-orange-600 text-orange-600 peer-checked:bg-orange-600 border-orange-600"></div> + <div class="bg-teal-600 hover:bg-teal-600 text-teal-600 peer-checked:bg-teal-600 border-teal-600"></div> + <div class="bg-lime-600 hover:bg-lime-600 text-lime-600 peer-checked:bg-lime-600 border-lime-600"></div> + <div class="bg-amber-600 hover:bg-amber-600 text-amber-600 peer-checked:bg-amber-600 border-amber-600"></div> <!-- 700 Colors --> <div class="bg-purple-700 hover:bg-purple-700 text-purple-700"></div> diff --git a/templates/watch/watch.html b/templates/watch/watch.html index 04e4f13..050b82e 100644 --- a/templates/watch/watch.html +++ b/templates/watch/watch.html @@ -5,6 +5,7 @@ <link rel="stylesheet" href="https://cdn.vidstack.io/player/audio.css" /> <link rel="stylesheet" href="https://cdn.vidstack.io/player/video.css" /> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css"/> +<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css"/> {% endblock css %} {% block content %} <div class="flex flex-col lg:flex-row mt-4 gap-2"> @@ -26,8 +27,8 @@ <h2 class="text-white text-xl font-bold mb-4">Episodes</h2> <div class="flex flex-col gap-2 h-full max-h-96 lg:h-[39vw] lg:max-h-[761px] overflow-y-auto"> {% for episode in all_episodes %} - {% if episode.number == current_episode_number %} - <a id="selected-episode" href="{% url "watch:watch_episode" anime.id episode.number %}{% if request.GET.mode %}?mode={{ request.GET.mode }}{% endif %}" class="flex flex-row gap-4 justify-between items-center w-full bg-{{ user.preferences.accent_colour }}-600 p-2 rounded hover:bg-{{ user.preferences.accent_colour }}-600 hover:bg-opacity-30"> + {% if not viaMal and episode.number == current_episode_number or viaMal and episode.episode.number == current_episode_number %} + <a id="selected-episode" href="{% if not viaMal %}{% url "watch:watch_episode" anime.id episode.number %}{% if request.GET.mode %}?mode={{ request.GET.mode }}{% endif %}{% else %}{% url "watch:watch_via_zid_mal_id" anime.id anime.z_anime_id %}?ep={{ episode.episode.identifier }}{% if request.GET.mode %}&mode={{ request.GET.mode }}{% endif %}{% endif %}" class="flex flex-row gap-4 justify-between items-center w-full bg-{{ user.preferences.accent_colour }}-600 p-2 rounded hover:bg-{{ user.preferences.accent_colour }}-600 hover:bg-opacity-30"> <span class="truncate max-w-full overflow-hidden text-ellipsis whitespace-nowrap">{{ episode.number }}. {{ episode.title }}</span> <span class="flex flex-row item-center gap-2"> @@ -61,7 +62,7 @@ </a> {% else %} - <a href="{% url "watch:watch_episode" anime.id episode.number %}{% if request.GET.mode %}?mode={{ request.GET.mode }}{% endif %}" class="flex flex-row justify-between w-full gap-4 {% if episode.number in watched_episodes %}bg-{{ user.preferences.accent_colour }}-600 bg-opacity-20{% else %}bg-white bg-opacity-10{% endif %} p-2 rounded hover:bg-{{ user.preferences.accent_colour }}-600 hover:bg-opacity-30"> + <a href="{% if not viaMal %}{% url "watch:watch_episode" anime.id episode.number %}{% if request.GET.mode %}?mode={{ request.GET.mode }}{% endif %}{% else %}{% url "watch:watch_via_zid_mal_id" anime.id anime.z_anime_id %}?ep={{ episode.episode.identifier }}{% if request.GET.mode %}&mode={{ request.GET.mode }}{% endif %}{% endif %}" class="flex flex-row justify-between w-full gap-4 {% if episode.number in watched_episodes %}bg-{{ user.preferences.accent_colour }}-600 bg-opacity-20{% else %}bg-white bg-opacity-10{% endif %} p-2 rounded hover:bg-{{ user.preferences.accent_colour }}-600 hover:bg-opacity-30"> <span class="truncate max-w-full overflow-hidden text-ellipsis whitespace-nowrap">{{ episode.number }}. {{ episode.title }}</span> <span class="flex flex-row item-center gap-2"> {% if episode.filler %} @@ -102,8 +103,8 @@ <div class="flex flex-row gap-2 items-center justify-between"> <h2 class="text-xl font-bold truncate max-w-full overflow-hidden text-ellipsis whitespace-nowrap">Episode {{ current_episode.number }} — {{ current_episode.title }}</h2> <div class="flex flex-row gap-1 items-center"> - <a href="{% if current_episode.number %}{% url "watch:watch_episode" anime.id current_episode.number %}?mode=sub{% endif %}" class="{% if mode == "sub" %}bg-{{ user.preferences.accent_colour }}-600{% else %}bg-white bg-opacity-10{% endif %} text-white text-sm font-bold px-4 py-2 rounded">Sub</a> - <a href="{% if current_episode.number %}{% url "watch:watch_episode" anime.id current_episode.number %}?mode=dub{% endif %}" class="{% if mode == "dub" %}bg-{{ user.preferences.accent_colour }}-600{% else %}bg-white bg-opacity-10{% endif %} text-white text-sm font-bold px-4 py-2 rounded">Dub</a> + <a href="{% if not viaMal %}{% url "watch:watch_episode" anime.id current_episode.number %}?mode=sub{% else %}{% url "watch:watch_via_zid_mal_id" anime.id anime.z_anime_id %}?ep={{ current_episode.episode.identifier }}&mode=sub{% endif %}" class="{% if mode == "sub" %}bg-{{ user.preferences.accent_colour }}-600{% else %}bg-white bg-opacity-10{% endif %} text-white text-sm font-bold px-4 py-2 rounded">Sub</a> + <a href="{% if not viaMal %}{% url "watch:watch_episode" anime.id current_episode.number %}?mode=dub{% else %}{% url "watch:watch_via_zid_mal_id" anime.id anime.z_anime_id %}?ep={{ current_episode.episode.identifier }}&mode=dub{% endif %}" class="{% if mode == "dub" %}bg-{{ user.preferences.accent_colour }}-600{% else %}bg-white bg-opacity-10{% endif %} text-white text-sm font-bold px-4 py-2 rounded">Dub</a> </div> </div> <p class="my-4"> @@ -440,8 +441,34 @@ {% endif %} </div> </div> + {% if seasons %} + <div class="my-8"> + <h2 class="text-xl font-bold text-white uppercase flex flex-row items-center gap-1 mb-4"> + Seasons + </h2> + <div class="flex flex-row gap-2 mt-4 overflow-x-auto no-scrollbar swiper seasonSwiper"> + <div class="swiper-wrapper"> + {% for season in seasons %} + <a href="{% url "watch:watch_via_zid" season.id %}" class="group rounded-lg aspect-video h-48 relative flex-shrink-0 overflow-hidden swiper-slide + {% if season.isCurrent %}border-4 border-{{ user.preferences.accent_colour }}-600{% endif %}"> + <div class="absolute inset-0 bg-center bg-cover transition-transform group-hover:scale-110" style="background-image: url('{{ season.poster }}')"></div> + <div class="absolute inset-0 bg-{{ user.preferences.accent_colour }}-600 opacity-0 group-hover:opacity-30 transition-opacity"></div> + <div class="absolute inset-0" style="background: linear-gradient(45deg, rgb(8, 8, 8) 15%, transparent 60%), linear-gradient(0deg, rgb(8, 8, 8) 0%, transparent 60%);"></div> + <div class="flex flex-col justify-end h-full p-2 relative z-10"> + <h1 class="text-xl font-bold truncate max-w-full overflow-hidden text-ellipsis whitespace-nowrap"> + {{ season.name }} + </h1> + <h2 class="font-bold truncate max-w-full overflow-hidden text-ellipsis whitespace-nowrap">{{ season.title }}</h2> + </div> + </a> + {% endfor %} + </div> + </div> + </div> + {% endif %} + {% if characters %} - <div class="my-4"> + <div class="my-8"> <h2 class="text-xl font-bold text-white uppercase flex flex-row items-center gap-1 mb-4"> Characters & Voice Actors </h2> @@ -463,6 +490,7 @@ </div> <div class="flex flex-col items-end"> {% for voice_actor in character.voiceActors|slice:":1" %} + {% if voice_actor.image %} <div class="flex flex-row gap-2 items-center mb-2"> <div class="flex flex-col gap-2 text-right"> <span class="font-bold"> @@ -476,6 +504,7 @@ </div> <img src="{{ voice_actor.image }}" alt="{{ voice_actor.name }}" class="rounded-full w-16 h-16 object-cover"/> </div> + {% endif %} {% endfor %} </div> </div> @@ -554,7 +583,7 @@ </div> {% endif %} {% if related.type == "TV" or related.type == "MOVIE" or related.type == "OVA" or related.type == "ONA" or related.type == "SPECIAL" or related.type == "TV_SHORT" %} - <a href="{% url 'watch:watch' related.id %}" class="flex flex-row w-full gap-4 bg-white bg-opacity-10 p-2 rounded hover:bg-{{ user.preferences.accent_colour }}-600 hover:bg-opacity-30"> + <a href="{% if not viaMal %}{% url 'watch:watch' related.id %}{% else %}{% url "watch:watch_via_zid" related.zid %}{% endif %}" class="flex flex-row w-full gap-4 bg-white bg-opacity-10 p-2 rounded hover:bg-{{ user.preferences.accent_colour }}-600 hover:bg-opacity-30"> <img src="{{ related.image }}" alt="{{ related.title.english }}" class="rounded-lg w-12 h-16 object-cover"/> <div class="flex flex-col gap-2"> <span class="font-bold flex gap-2 flex-row items-center"> @@ -604,12 +633,14 @@ {{ related.rating }} </span> {% endif %} + {% if related.relationType %} <span class="text-xs font-bold bg-white bg-opacity-10 p-1 rounded flex items-center gap-1"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-4"> <path d="m11.645 20.91-.007-.003-.022-.012a15.247 15.247 0 0 1-.383-.218 25.18 25.18 0 0 1-4.244-3.17C4.688 15.36 2.25 12.174 2.25 8.25 2.25 5.322 4.714 3 7.688 3A5.5 5.5 0 0 1 12 5.052 5.5 5.5 0 0 1 16.313 3c2.973 0 5.437 2.322 5.437 5.25 0 3.925-2.438 7.111-4.739 9.256a25.175 25.175 0 0 1-4.244 3.17 15.247 15.247 0 0 1-.383.219l-.022.012-.007.004-.003.001a.752.752 0 0 1-.704 0l-.003-.001Z" /> </svg> {{ related.relationType }} </span> + {% endif %} {% if related.episodes %} <span class="text-xs font-bold bg-white bg-opacity-10 p-1 rounded flex items-center gap-1"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4"> @@ -632,8 +663,8 @@ </div> <div class="flex flex-col gap-2"> {% for recommendation in recommendations|slice:":10" %} - {% if recommendation.id %} - <a href="{% url 'watch:watch' recommendation.id %}" class="flex flex-row w-full gap-4 bg-white bg-opacity-10 p-2 rounded hover:bg-{{ user.preferences.accent_colour }}-600 hover:bg-opacity-30"> + {% if recommendation.id or recommendation.zid %} + <a href="{% if not viaMal %}{% url 'watch:watch' recommendation.id %}{% else %}{% url "watch:watch_via_zid" recommendation.zid %}{% endif %}" class="flex flex-row w-full gap-4 bg-white bg-opacity-10 p-2 rounded hover:bg-{{ user.preferences.accent_colour }}-600 hover:bg-opacity-30"> <img src="{{ recommendation.image }}" alt="{{ recommendation.title.english }}" class="rounded-lg w-12 h-16 object-cover"/> <div class="flex flex-col gap-2 max-w-[calc(100%-4rem)]"> <span class="font-bold flex gap-2 flex-row items-center"> @@ -817,7 +848,14 @@ observer.observe(episodesSelect, {childList: true, characterData: true}); {% endif %} </script> - +<script src="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js"></script> +<script> + const seasonSwiper = new Swiper(".seasonSwiper", { + slidesPerView: 'auto', + spaceBetween: 8, + freeMode: true, + }); + </script> <script type="module"> const introStart = {{ streaming_data.intro.start }}; const introEnd = {{ streaming_data.intro.end }}; @@ -1000,6 +1038,7 @@ }); {% endif %} + {% if not viaMal %} let lastTime = 0; setInterval(() => { const { @@ -1029,6 +1068,6 @@ lastTime = currentTime; }, 30000); - + {% endif %} </script> {% endblock scripts %} diff --git a/watch/urls.py b/watch/urls.py index 4e0f66d..8df0660 100644 --- a/watch/urls.py +++ b/watch/urls.py @@ -4,7 +4,9 @@ from . import views app_name = "watch" urlpatterns = [ - path('<int:anime_id>/', views.watch, name='watch'), - path('<int:anime_id>/<int:episode>/', views.watch, name='watch_episode'), - path('update_watch_history/', views.update_episode_watch_time, name='update_watch_history'), + path('<int:anime_id>', views.watch, name='watch'), + path('<int:anime_id>/<int:episode>', views.watch, name='watch_episode'), + path('zid:<str:zid>', views.watch_via_zid, name='watch_via_zid'), + path('update_watch_history', views.update_episode_watch_time, name='update_watch_history'), + path('malId:<int:mal_id>$zid:<str:zid>', views.watch_via_zid_mal_id, name='watch_via_zid_mal_id'), # if anilist id is not available ] diff --git a/watch/views.py b/watch/views.py index a8a5232..b988402 100644 --- a/watch/views.py +++ b/watch/views.py @@ -2,15 +2,16 @@ import datetime from datetime import datetime as dt import os from django.http import Http404, JsonResponse +from django.urls import reverse import dotenv from django.shortcuts import get_object_or_404, render, redirect import requests from authentication.utils import get_single_anime_mal +from watch.tmdbmapper import parse_title_and_season from watch.utils import get_all_episode_metadata, get_from_redis_cache, store_in_redis_cache, update_anime_user_history, get_anime_user_history from watch.models import Anime, AnimeEpisode, AnimeTitle, AnimeTrailer, AnimeGenre, AnimeStudio from django.db import transaction -from django.db.models import Q -from collections import defaultdict +from authentication.models import User import json dotenv.load_dotenv() @@ -45,11 +46,33 @@ def get_info_by_zid(zid): return anime_selected -def get_episodes_by_zid(anime): - base_url = f"{os.getenv('ZORO_URL')}/anime/episodes/{anime.z_anime_id}" - response = requests.get(base_url) - fetched_episodes = response.json() +def get_seasons_by_zid(zid): + if not zid: + return [] + fetched_info = get_info_by_zid(zid) + seasons = fetched_info["seasons"] + + for season in seasons: + season["poster"] = season["poster"].replace("100x200/100", "400x800/100") + + return seasons + +def get_episodes_by_zid(z_anime_id): + cache_key = f"anime_{z_anime_id}_episodes" + try: + fetched_episodes = get_from_redis_cache(cache_key) + fetched_episodes = json.loads(fetched_episodes) + except: + base_url = f"{os.getenv('ZORO_URL')}/anime/episodes/{z_anime_id}" + response = requests.get(base_url) + fetched_episodes = response.json() + store_in_redis_cache(cache_key, json.dumps(fetched_episodes), 3600 * 12) + + return fetched_episodes + +def get_episodes_and_metadata(anime): + fetched_episodes = get_episodes_by_zid(anime.z_anime_id) anime_data = { "id": anime.id, "title": { @@ -198,7 +221,7 @@ def update_anime_episodes(anime): if not anime.z_anime_id: return anime - fetched_episodes = get_episodes_by_zid(anime) + fetched_episodes = get_episodes_and_metadata(anime) with transaction.atomic(): # Update anime's total episodes @@ -299,6 +322,8 @@ def watch(request, anime_id, episode=None): if anime and episode_data: update_anime_user_history(request.user, anime, episode_data, current_watched_time) + seasons = get_seasons_by_zid(anime.z_anime_id) + context = { "anime": anime, "current_episode_number": episode, @@ -314,6 +339,7 @@ def watch(request, anime_id, episode=None): "current_watched_time": current_watched_time, "mal_data": mal_data, "mal_episode_range": range(1, mal_data["num_episodes"] + 1) if mal_data else None, + "seasons": seasons, } if "nextAiringEpisode" in anime_fetched: @@ -337,3 +363,202 @@ def update_episode_watch_time(request): return JsonResponse({"status": "success"}) else: return JsonResponse({"status": "error", "message": "User not authenticated"}) + +def watch_via_zid(request, zid): + cache_key = f"anime_{zid}_anilist_lookup" + anilist_id = get_from_redis_cache(cache_key) + + # See if anilist id is present in the info from zoro + if not anilist_id: + anime_selected = get_info_by_zid(zid) + anilist_id = anime_selected["anime"]["info"]["anilistId"] + # store_in_redis_cache(cache_key, anilist_id, 3600 * 24 * 30) + + # If not see if we can find the anilist id from the mal id using anilist graphql + # this will almost always work and we wont need to search for the anime by name + if not anilist_id: + mal_id = anime_selected["anime"]["info"]["malId"] + print("Searching using graphql. Mal id:", mal_id) + anilist_graphql_url = "https://graphql.anilist.co" + query = """ + query {{ + Media(idMal: {mal_id}, type: ANIME) {{ + id + }} + }} + """.format(mal_id=mal_id) + + response = requests.post(anilist_graphql_url, json={"query": query}) + response = response.json() + + if not "errors" in response: + anilist_id = response["data"]["Media"]["id"] + # store_in_redis_cache(cache_key, anilist_id, 3600 * 24 * 30) + + if not anilist_id: + anime_name = anime_selected['anime']['info']['name'] + anime_name = parse_title_and_season(anime_name)["show_name"] + consumet_search_url = f"{os.getenv('CONSUMET_URL')}/meta/anilist/advanced-search?query={anime_name}&provider=zoro" + response = requests.get(consumet_search_url) + anime_search_results = response.json() + + # compare where mal id is same and return the anilist id + for result in anime_search_results["results"]: + if result["malId"] == mal_id: + anilist_id = result["id"] + # store_in_redis_cache(cache_key, anilist_id, 3600 * 24 * 30) + break + + print(anilist_id) + if anilist_id: + return redirect("watch:watch", anime_id=anilist_id) + else: + return redirect("watch:watch_via_zid_mal_id", mal_id=mal_id, zid=zid) + +# same thing as watch, but with mal id and zid since anilist id is not available +# context remains the same but data is not saved in database +def watch_via_zid_mal_id(request, mal_id, zid): + anime_info = get_info_by_zid(zid) + + mal_access_token = request.user.mal_access_token + if not mal_access_token: + u = User.objects.filter(mal_access_token__isnull=False).first() + mal_access_token = u.mal_access_token + + anime_mal_info = get_single_anime_mal(mal_access_token, mal_id) + anime_episodes = get_episodes_by_zid(zid) + for index, episode in enumerate(anime_episodes["episodes"]): + episode_identifier = episode["episodeId"].split("?ep=")[1] + anime_episodes["episodes"][index]["episode"] = { + "identifier": episode_identifier, + "number": index + 1, + } + print(anime_episodes["episodes"][index]["episodeId"]) + + current_episode_number = 1 + ep = request.GET.get("ep", None) + + if ep: + current_episode_number = next((i + 1 for i, item in enumerate(anime_episodes["episodes"]) if item["episode"]["identifier"] == ep), 1) + else: + ep = anime_episodes["episodes"][0]["episode"]["identifier"] + return redirect(reverse("watch:watch_via_zid_mal_id", args=[mal_id, zid]) + f"?ep={ep}") + + current_episode = anime_episodes["episodes"][int(current_episode_number) - 1] + + mode = request.GET.get("mode", request.user.preferences.default_language) + if mode == "dub" and (not anime_info["anime"]["info"]["stats"]["episodes"]["dub"] or anime_info["anime"]["info"]["stats"]["episodes"]["dub"] < current_episode_number): + mode = "sub" + + streaming_data = get_episode_streaming_data(current_episode["episodeId"], mode) + + if streaming_data and "tracks" in streaming_data and not any(t["kind"] == "captions" for t in streaming_data["tracks"]) and mode == "dub" and request.user.preferences.ingrain_sub_subtitles_in_dub: + sub_streaming_data = get_episode_streaming_data(current_episode["episodeId"], "sub") + captions = [t for t in sub_streaming_data["tracks"] if t["kind"] == "captions"] + if captions: + streaming_data["tracks"].extend(captions) + + anime = { + "id": mal_id, + "malId": mal_id, + "z_anime_id": zid, + "description": anime_info["anime"]["info"]["description"], + "image": anime_info["anime"]["info"]["poster"].replace("300x400/100", "600x800/100"), + "countryOfOrigin": "JP", + "titles": { + "english": anime_mal_info["alternative_titles"]["en"], + "romaji": anime_mal_info["title"], + "native": anime_mal_info["alternative_titles"]["ja"] + }, + "type": anime_mal_info["media_type"].replace("_", " ").title(), + "popularity": anime_mal_info["popularity"], + "releaseDate": anime_mal_info["start_date"].split("-")[0], + "totalEpisodes": anime_mal_info["num_episodes"], + "currentEpisode": len(anime_episodes["episodes"]), + "rating": anime_mal_info["mean"], + "duration": anime_mal_info["average_episode_duration"] // 60 + 1, + "genres": { + "all": anime_mal_info["genres"] + }, + "status": anime_mal_info["status"].replace("_", " ").title(), + "season": anime_mal_info["start_season"]["season"].title(), + "studios": { + "all": anime_mal_info["studios"] + }, + "sub": anime_info["anime"]["info"]["stats"]["episodes"].get("sub", 0), + "dub": anime_info["anime"]["info"]["stats"]["episodes"].get("dub", 0), + } + + related = [] + for r in anime_info["relatedAnimes"]: + rd = { + "zid": r["id"], + "image": r["poster"].replace("300x400/100", "600x800/100"), + "title": { + "english": r["name"], + "romaji": r["jname"] + }, + "episodes": r["episodes"]["sub"], + "type": r["type"], + } + + related.append(rd) + + recommended = [] + for r in anime_info["recommendedAnimes"]: + rd = { + "zid": r["id"], + "image": r["poster"].replace("300x400/100", "600x800/100"), + "title": { + "english": r["name"], + "romaji": r["jname"] + }, + "episodes": r["episodes"]["sub"], + "type": r["type"], + } + + characters = [] + for c in anime_info["anime"]["info"]["charactersVoiceActors"]: + cd = { + "name": { + "full": c["character"]["name"], + "natve": c["character"]["name"], + }, + "image": c["character"]["poster"].replace("100x100/100", "200x200/100"), + "role": c["character"]["cast"], + "voiceActors": [ + { + "name": { + "full": c["voiceActor"]["name"], + "native": c["voiceActor"]["name"], + }, + "image": c["voiceActor"]["poster"].replace("100x100/100", "200x200/100"), + "language": "Japanese", + } + ], + } + + characters.append(cd) + + context = { + "anime": anime, + "current_episode_number": current_episode_number, + "current_episode": current_episode, + "all_episodes": anime_episodes["episodes"], + "streaming_data": streaming_data, + "stream_url": streaming_data["sources"][0]["url"] if streaming_data and "sources" in streaming_data else None, + "watched_episodes": [], + "current_watched_time": 0, + "mode": mode, + "seasons": get_seasons_by_zid(zid), + "viaMal": True, + "related": related, + "recommendations": recommended, + "characters": characters, + } + + if request.user.mal_access_token: + context["mal_data"] = anime_mal_info + context["mal_episode_range"] = range(1, anime_mal_info["num_episodes"] + 1) + + return render(request, "watch/watch.html", context) |
