diff options
| -rw-r--r-- | requirements.txt | 1 | ||||
| -rw-r--r-- | static/css/main.css | 56 | ||||
| -rw-r--r-- | templates/watch/watch.html | 96 | ||||
| -rw-r--r-- | watch/views.py | 41 | ||||
| -rw-r--r-- | yugen/settings.py | 4 |
5 files changed, 196 insertions, 2 deletions
diff --git a/requirements.txt b/requirements.txt index 71ad648..2cf0e02 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,4 @@ django python-dotenv psycopg2-binary requests +django-cors-headers diff --git a/static/css/main.css b/static/css/main.css index 65d12be..09a4a9b 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -749,6 +749,10 @@ video { display: none; } +.aspect-video { + aspect-ratio: 16 / 9; +} + .size-3 { width: 0.75rem; height: 0.75rem; @@ -804,6 +808,18 @@ video { height: 100%; } +.h-screen { + height: 100vh; +} + +.h-\[56\.25\%\] { + height: 56.25%; +} + +.h-0 { + height: 0px; +} + .max-h-24 { max-height: 6rem; } @@ -857,6 +873,14 @@ video { width: max-content; } +.w-1\/4 { + width: 25%; +} + +.w-3\/4 { + width: 75%; +} + .max-w-7xl { max-width: 80rem; } @@ -978,6 +1002,10 @@ video { overflow: auto; } +.overflow-y-auto { + overflow-y: auto; +} + .break-words { overflow-wrap: break-word; } @@ -1289,6 +1317,26 @@ video { background-color: rgb(250 204 21 / var(--tw-bg-opacity)); } +.bg-gray-500 { + --tw-bg-opacity: 1; + background-color: rgb(107 114 128 / var(--tw-bg-opacity)); +} + +.bg-gray-700 { + --tw-bg-opacity: 1; + background-color: rgb(55 65 81 / var(--tw-bg-opacity)); +} + +.bg-gray-800 { + --tw-bg-opacity: 1; + background-color: rgb(31 41 55 / var(--tw-bg-opacity)); +} + +.bg-gray-900 { + --tw-bg-opacity: 1; + background-color: rgb(17 24 39 / var(--tw-bg-opacity)); +} + .bg-opacity-10 { --tw-bg-opacity: 0.1; } @@ -1405,6 +1453,10 @@ video { padding-top: 0.25rem; } +.pb-\[56\.25\%\] { + padding-bottom: 56.25%; +} + .text-center { text-align: center; } @@ -2133,6 +2185,10 @@ main { display: none; } + .lg\:h-auto { + height: auto; + } + .lg\:w-1\/2 { width: 50%; } diff --git a/templates/watch/watch.html b/templates/watch/watch.html index 7708ade..4961e61 100644 --- a/templates/watch/watch.html +++ b/templates/watch/watch.html @@ -1,5 +1,99 @@ {% extends "partials/base.html" %} +{% block css %} +<link rel="stylesheet" href="https://cdn.vidstack.io/player/theme.css" /> +<link rel="stylesheet" href="https://cdn.vidstack.io/player/audio.css" /> +<link rel="stylesheet" href="https://cdn.vidstack.io/player/video.css" /> +{% endblock css %} {% block content %} -Watch {{ anime_id }}/{{ episode }} +<div class="flex flex-col lg:flex-row mt-4 gap-2"> + <!-- Video Player Section (75%) --> + <div class="w-full lg:w-3/4"> + <!-- Set a fixed height based on the 16:9 aspect ratio --> + <!-- This grey div is a placeholder for the video player --> + {% comment %} <div class="w-full h-0 lg:h-auto aspect-video bg-gray-500 flex items-center justify-center rounded"> {% endcomment %} + <div id="video-player" class="aspect-video"> + </div> + </div> + + <!-- Episode List Section (25%) --> + <div class="w-full lg:w-1/4 px-2 overflow-y-auto"> + <h2 class="text-white text-xl font-bold mb-4">Episodes</h2> + <ul class="space-y-2"> + <!-- Example Episode Items --> + <li class="bg-gray-700 text-white p-2 rounded">Episode 1: The Beginning</li> + <li class="bg-gray-700 text-white p-2 rounded">Episode 2: The Journey</li> + <li class="bg-gray-700 text-white p-2 rounded">Episode 3: The Encounter</li> + <!-- Add more episodes here --> + </ul> + </div> +</div> + +{{ anime }} + {% endblock content %} +{% block scripts %} +<script type="module"> + import { VidstackPlayer, VidstackPlayerLayout } from 'https://cdn.vidstack.io/player'; + + const layout = new VidstackPlayerLayout({ + {% for track in episode.subtitles %} + {% if track.lang == "Thumbnails" %} + thumbnails: '{{ track.url }}', + {% endif %} + {% endfor %} + }); + + // thumbnails: 'https://files.vidstack.io/sprite-fight/thumbnails.vtt', + const player = await VidstackPlayer.create({ + target: '#video-player', + autoStartLoad: true, + src: '{{ stream_url }}', + viewType: 'video', + streamType: 'on-demand', + logLevel: 'warn', + crossOrigin: true, + playsInline: true, + title: '{% if user.preferences.title_language == "english" and anime.title.english %}{{ anime.title.english }}{% elif user.preferences.title_language == "native" and anime.title.native %}{{ anime.title.native }}{% else %}{{ anime.title.romaji }}{% endif %} — Episode {{ current_episode }} ({% if request.GET.mode == "dub" %}Dub{% else %}Sub{% endif %})', + subtitlePreference: { + lang: 'English', + }, + layout, + tracks: [ + {% for track in episode.subtitles %} + {% if track.lang != "Thumbnails" %} + { + src: '{{ track.url }}', + label: '{{ track.lang }}', + kind: 'subtitles', + type: 'vtt', + lang: '{{ track.lang }}', + default: {% if forloop.first %}true{% else %}false{% endif %}, + }, + {% endif %} + {% endfor %} + ], + }); + + player.addEventListener('auto-play', (event) => { + const requestEvent = event.request; + }); + + // autoplay has failed. + player.addEventListener('auto-play-fail', (event) => { + const requestEvent = event.request; + console.log(event.detail.muted); // was media muted? + console.log(event.detail.error); // media error + }); + /*const url = '{{ stream_url }}'; + + const player = await VidstackPlayer.create({ + target: '#video-player', + title: '{{ anime.title }}', + src: url, + layout: new VidstackPlayerLayout({ + thumbnails: 'https://files.vidstack.io/sprite-fight/thumbnails.vtt', + }), + });*/ + </script> +{% endblock scripts %} diff --git a/watch/views.py b/watch/views.py index cd85a8a..157cc18 100644 --- a/watch/views.py +++ b/watch/views.py @@ -1,6 +1,45 @@ +import os +import dotenv from django.shortcuts import render, redirect +import requests + +dotenv.load_dotenv() def watch(request, anime_id, episode=None): if not episode or episode < 1: return redirect("watch:watch_episode", anime_id=anime_id, episode=1) - return render(request, "watch/watch.html", {"anime_id": anime_id, "episode": episode})
\ No newline at end of file + + mode = request.GET.get("mode", "sub") + + base_url = f"{os.getenv("CONSUMET_URL")}/meta/anilist/data/{anime_id}?provider=zoro" + response = requests.get(base_url) + anime_data = response.json() + + base_url = f"{os.getenv("CONSUMET_URL")}/anime/zoro/{anime_data["title"]["english"]}?page=1" + response = requests.get(base_url) + anime_search_result = response.json()["results"][0] + anime_fetch_id = anime_search_result["id"] + + base_url = f"{os.getenv("CONSUMET_URL")}/anime/zoro/info?id={anime_fetch_id}" + response = requests.get(base_url) + anime_info = response.json() + episodes = anime_info["episodes"] + + if episode > anime_info["totalEpisodes"]: + return redirect("watch:watch_episode", anime_id=anime_id, episode=episodes) + + # episode_d = [episode for episode in episodes if episode["number"] == episode] + + ed = None + for e in episodes: + if e["number"] == episode: + ed = e + break + + base_url = f"{os.getenv("CONSUMET_URL")}/anime/zoro/watch?episodeId={ed['id'].replace("sub", "").replace("dub", "").replace("both", "")}{mode}" + response = requests.get(base_url) + episode_data = response.json() + + print(episode_data) + + return render(request, "watch/watch.html", { "anime": anime_data, "episode": episode_data, "episodes": episodes, "current_episode": episode, "stream_url": episode_data["sources"][0]["url"] })
\ No newline at end of file diff --git a/yugen/settings.py b/yugen/settings.py index f1d9422..748582f 100644 --- a/yugen/settings.py +++ b/yugen/settings.py @@ -42,6 +42,7 @@ INSTALLED_APPS = [ "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", + "corsheaders", "homepage", "authentication", "watch", @@ -51,6 +52,7 @@ INSTALLED_APPS = [ MIDDLEWARE = [ "django.middleware.security.SecurityMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", + "corsheaders.middleware.CorsMiddleware", "django.middleware.common.CommonMiddleware", "django.middleware.csrf.CsrfViewMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", @@ -61,6 +63,8 @@ MIDDLEWARE = [ "middleware.preferences.PreferencesMiddleware", ] +CORS_ORIGIN_ALLOW_ALL = True + ROOT_URLCONF = "yugen.urls" TEMPLATES = [ |
