mirror of
https://github.com/Arrowar/StreamingCommunity.git
synced 2025-06-07 12:05:35 +00:00
implemented UI for media details (TODO add download capabilities)
This commit is contained in:
parent
6b22a90688
commit
0e93552e69
@ -55,44 +55,51 @@ class SearchView(viewsets.ViewSet):
|
|||||||
try:
|
try:
|
||||||
match self.type_media:
|
match self.type_media:
|
||||||
case "TV":
|
case "TV":
|
||||||
self.site_version, self.domain = get_version_and_domain()
|
def stream_episodes():
|
||||||
|
self.site_version, self.domain = get_version_and_domain()
|
||||||
|
|
||||||
video_source = VideoSource()
|
video_source = VideoSource()
|
||||||
video_source.set_url_base_name(STREAM_SITE_NAME)
|
video_source.set_url_base_name(STREAM_SITE_NAME)
|
||||||
video_source.set_version(self.site_version)
|
video_source.set_version(self.site_version)
|
||||||
video_source.set_domain(self.domain)
|
video_source.set_domain(self.domain)
|
||||||
video_source.set_series_name(self.media_slug)
|
video_source.set_series_name(self.media_slug)
|
||||||
video_source.set_media_id(self.media_id)
|
video_source.set_media_id(self.media_id)
|
||||||
|
|
||||||
video_source.collect_info_seasons()
|
video_source.collect_info_seasons()
|
||||||
seasons_count = video_source.obj_title_manager.get_length()
|
seasons_count = video_source.obj_title_manager.get_length()
|
||||||
|
|
||||||
episodes = {}
|
episodes = {}
|
||||||
for i_season in range(1, seasons_count + 1):
|
for i_season in range(1, seasons_count + 1):
|
||||||
video_source.obj_episode_manager.clear()
|
video_source.obj_episode_manager.clear()
|
||||||
video_source.collect_title_season(i_season)
|
video_source.collect_title_season(i_season)
|
||||||
episodes_count = video_source.obj_episode_manager.get_length()
|
episodes_count = video_source.obj_episode_manager.get_length()
|
||||||
episodes[i_season] = {}
|
episodes[i_season] = {}
|
||||||
for i_episode in range(1, episodes_count + 1):
|
for i_episode in range(1, episodes_count + 1):
|
||||||
episode = video_source.obj_episode_manager.episodes[
|
episode = video_source.obj_episode_manager.episodes[
|
||||||
i_episode - 1
|
i_episode - 1
|
||||||
]
|
]
|
||||||
episodes[i_season][i_episode] = episode.__dict__
|
episodes[i_season][i_episode] = episode.__dict__
|
||||||
|
|
||||||
|
yield f'{json.dumps({"episodes": episodes})}\n\n'
|
||||||
|
|
||||||
|
response = StreamingHttpResponse(stream_episodes(), content_type='text/event-stream')
|
||||||
|
return response
|
||||||
|
|
||||||
return Response({"episodes": episodes})
|
|
||||||
case "TV_ANIME":
|
case "TV_ANIME":
|
||||||
def stream_episodes():
|
def stream_episodes():
|
||||||
episodes_downloader = EpisodeDownloader(self.media_id, self.media_slug)
|
episodes_downloader = EpisodeDownloader(self.media_id, self.media_slug)
|
||||||
episoded_count = episodes_downloader.get_count_episodes()
|
episoded_count = episodes_downloader.get_count_episodes()
|
||||||
|
|
||||||
for i in range(1, episoded_count+1):
|
for i in range(1, episoded_count + 1):
|
||||||
episode_info = episodes_downloader.get_info_episode(index_ep=i)
|
episode_info = episodes_downloader.get_info_episode(index_ep=i)
|
||||||
episode_info["episode_id"] = i
|
episode_info["episode_id"] = i
|
||||||
|
episode_info["episode_total"] = episoded_count
|
||||||
print(f"Getting episode {i} of {episoded_count} info...")
|
print(f"Getting episode {i} of {episoded_count} info...")
|
||||||
yield f'data: {json.dumps(episode_info)}\n\n'
|
yield f'{json.dumps(episode_info)}\n\n'
|
||||||
|
|
||||||
response = StreamingHttpResponse(stream_episodes(), content_type='text/event-stream')
|
response = StreamingHttpResponse(stream_episodes(), content_type='text/event-stream')
|
||||||
return response
|
return response
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return Response(
|
return Response(
|
||||||
{
|
{
|
||||||
@ -112,7 +119,8 @@ class DownloadView(viewsets.ViewSet):
|
|||||||
self.type_media = request.data.get("type_media").upper()
|
self.type_media = request.data.get("type_media").upper()
|
||||||
self.download_id = request.data.get("download_id")
|
self.download_id = request.data.get("download_id")
|
||||||
|
|
||||||
self.site_version, self.domain = get_version_and_domain()
|
if self.type_media in ["TV", "MOVIE"]:
|
||||||
|
self.site_version, self.domain = get_version_and_domain()
|
||||||
|
|
||||||
response_dict = {"error": "No media found with that search query"}
|
response_dict = {"error": "No media found with that search query"}
|
||||||
|
|
||||||
|
@ -3,71 +3,14 @@ import { RouterLink, RouterView } from 'vue-router'
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<!--<RouterLink to="/">Home</RouterLink>-->
|
|
||||||
|
|
||||||
<RouterView />
|
<RouterView />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style>
|
||||||
header {
|
#app {
|
||||||
line-height: 1.5;
|
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||||
max-height: 100vh;
|
-webkit-font-smoothing: antialiased;
|
||||||
}
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
margin-top: 60px;
|
||||||
.logo {
|
|
||||||
display: block;
|
|
||||||
margin: 0 auto 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav {
|
|
||||||
width: 100%;
|
|
||||||
font-size: 12px;
|
|
||||||
text-align: center;
|
|
||||||
margin-top: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav a.router-link-exact-active {
|
|
||||||
color: var(--color-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
nav a.router-link-exact-active:hover {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav a {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 0 1rem;
|
|
||||||
border-left: 1px solid var(--color-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
nav a:first-of-type {
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 1024px) {
|
|
||||||
header {
|
|
||||||
display: flex;
|
|
||||||
place-items: center;
|
|
||||||
padding-right: calc(var(--section-gap) / 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo {
|
|
||||||
margin: 0 2rem 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
header .wrapper {
|
|
||||||
display: flex;
|
|
||||||
place-items: flex-start;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav {
|
|
||||||
text-align: left;
|
|
||||||
margin-left: -1rem;
|
|
||||||
font-size: 1rem;
|
|
||||||
|
|
||||||
padding: 1rem 0;
|
|
||||||
margin-top: 1rem;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -17,15 +17,12 @@ export default function search(query: string, type: string) : Promise<MediaItemR
|
|||||||
|
|
||||||
export async function getEpisodesInfo(mediaId: number, mediaSlug: string, mediaType: string): Promise<Response> {
|
export async function getEpisodesInfo(mediaId: number, mediaSlug: string, mediaType: string): Promise<Response> {
|
||||||
const url = `${BASE_URL}/search/get_episodes_info?media_id=${mediaId}&media_slug=${mediaSlug}&type_media=${mediaType}`;
|
const url = `${BASE_URL}/search/get_episodes_info?media_id=${mediaId}&media_slug=${mediaSlug}&type_media=${mediaType}`;
|
||||||
if (mediaType === 'TV_ANIME') {
|
return await fetch(url, {
|
||||||
return await fetch(url, {
|
method: 'GET',
|
||||||
method: 'GET',
|
headers: {
|
||||||
headers: {
|
'Content-Type': 'text/event-stream'
|
||||||
'Content-Type': 'text/event-stream'
|
}
|
||||||
}
|
});
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return Promise.resolve(new Response());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
@ -24,7 +24,7 @@ export interface MediaItemResponse {
|
|||||||
media: MediaItem[];
|
media: MediaItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EpisodeAnime {
|
export interface Episode {
|
||||||
id: number;
|
id: number;
|
||||||
anime_id: number;
|
anime_id: number;
|
||||||
user_id: number | null;
|
user_id: number | null;
|
||||||
@ -38,8 +38,21 @@ export interface EpisodeAnime {
|
|||||||
file_name: string;
|
file_name: string;
|
||||||
tg_post: number;
|
tg_post: number;
|
||||||
episode_id: number;
|
episode_id: number;
|
||||||
|
episode_total: number;
|
||||||
|
name: string; // TV Show exclusive
|
||||||
|
plot: string; // TV Show exclusive
|
||||||
|
duration: number; // TV Show exclusive
|
||||||
|
season_id: number; // TV Show exclusive
|
||||||
|
created_by: any; // TV Show exclusive
|
||||||
|
updated_at: string; // TV Show exclusive
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EpisodeAnimeResponse {
|
export interface Season {
|
||||||
episodes: EpisodeAnime[];
|
[key: string]: {
|
||||||
|
[key: string]: Episode;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SeasonResponse {
|
||||||
|
episodes: Season;
|
||||||
}
|
}
|
@ -1,32 +1,47 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import type { EpisodeAnime, MediaItem } from "@/api/interfaces";
|
import type {Episode, MediaItem, Season, SeasonResponse} from "@/api/interfaces";
|
||||||
import { onMounted, ref, onUnmounted } from "vue";
|
import { onMounted, ref } from "vue";
|
||||||
import { getEpisodesInfo } from "@/api/api";
|
import { getEpisodesInfo } from "@/api/api";
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
const item: MediaItem = JSON.parse(<string>route.params.item)
|
const item: MediaItem = JSON.parse(<string>route.params.item)
|
||||||
const imageUrl: string = <string>route.params.imageUrl
|
const imageUrl: string = <string>route.params.imageUrl
|
||||||
const episodes = ref<EpisodeAnime[]>([])
|
const animeEpisodes = ref<Episode[]>([])
|
||||||
|
const tvShowEpisodes = ref<any[]>([])
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
if (item.type !== 'TV_ANIME' && item.type !== 'TV') {
|
if (['MOVIE', 'OVA'].includes(item.type)) {
|
||||||
return
|
return
|
||||||
}
|
} else {
|
||||||
loading.value = true
|
loading.value = true;
|
||||||
const response = await getEpisodesInfo(item.id, item.slug, item.type)
|
const response = await getEpisodesInfo(item.id, item.slug, item.type)
|
||||||
if (response && response.body) {
|
if (response && response.body) {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
|
const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
|
||||||
while (true) {
|
while (true) {
|
||||||
const {value, done} = await reader.read();
|
const {value, done} = await reader.read();
|
||||||
if (done) {
|
if (done) {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
if (item.type === 'TV_ANIME') {
|
||||||
|
const episodesData:Episode = JSON.parse(value.trim());
|
||||||
|
animeEpisodes.value.push(episodesData);
|
||||||
|
} else {
|
||||||
|
const episodesData:SeasonResponse = JSON.parse(value.trim());
|
||||||
|
for (const seasonKey in episodesData.episodes) {
|
||||||
|
const season = episodesData.episodes[seasonKey];
|
||||||
|
const episodes:Episode[] = [];
|
||||||
|
for (const episodeKey in season) {
|
||||||
|
const episode:Episode = season[episodeKey];
|
||||||
|
episodes.push(episode);
|
||||||
|
}
|
||||||
|
tvShowEpisodes.value.push(episodes);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const episodesData:EpisodeAnime = JSON.parse(value.split("a:")[1].trim());
|
|
||||||
episodes.value.push(episodesData);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,29 +51,57 @@ onMounted(async () => {
|
|||||||
<template>
|
<template>
|
||||||
<div class="details-container">
|
<div class="details-container">
|
||||||
<div class="details-card">
|
<div class="details-card">
|
||||||
|
|
||||||
|
<!--HEADER SECTION-->
|
||||||
<div class="details-header">
|
<div class="details-header">
|
||||||
<img :src="imageUrl" :alt="item.name" class="details-image" />
|
<img :src="imageUrl" :alt="item.name" class="details-image" />
|
||||||
<div class="details-title-container">
|
<div class="details-title-container">
|
||||||
<h1 class="details-title">{{ item.name }}</h1>
|
<h1 class="details-title">{{ item.name }}</h1>
|
||||||
<h3>★ {{ item.score }}</h3>
|
<h3>★ {{ item.score }}</h3>
|
||||||
<div class="details-description">
|
<div class="details-description">
|
||||||
<p>{{ item.plot }}</p>
|
<p v-if="item.type == 'TV_ANIME'">{{ item.plot }}</p>
|
||||||
|
<p v-else-if="tvShowEpisodes.length > 0">{{ tvShowEpisodes[0][0].plot }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
<h3 v-if="animeEpisodes.length > 0 && !loading">Numero episodi: {{ animeEpisodes[0].episode_total }}</h3>
|
||||||
|
<h3 v-if="tvShowEpisodes.length > 0 && !loading">Numero stagioni: {{ tvShowEpisodes.length }}</h3>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="episodes-container">
|
|
||||||
<div v-if="!loading" v-for="episode in episodes" :key="episode.id" class="episode-item">
|
<!--SERIES SECTION-->
|
||||||
<div class="episode-title">{{ episode.number }} - {{ episode.file_name }}</div>
|
<div v-if="!loading && ['TV_ANIME', 'TV'].includes(item.type)" :class="item.type == 'TV_ANIME' ? 'episodes-container' : 'season-container'">
|
||||||
</div>
|
<div v-if="animeEpisodes.length == 0 && tvShowEpisodes.length == 0">
|
||||||
<div v-else>
|
<p>Non ci sono episodi...</p>
|
||||||
<p>Loading...</p>
|
</div>
|
||||||
</div>
|
<div v-else-if="item.type == 'TV_ANIME'" v-for="episode in animeEpisodes" :key="episode.id" class="episode-item">
|
||||||
|
<div class="episode-title">Episodio {{ episode.number }}</div>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="item.type == 'TV'" v-for="(season, index) in tvShowEpisodes" class="season-item">
|
||||||
|
<div class="season-title">Stagione {{ index + 1 }}</div>
|
||||||
|
<div class="episode-container">
|
||||||
|
<div v-for="episode in season" :key="episode.id" class="episode-item">
|
||||||
|
<div class="episode-title">
|
||||||
|
Episodio {{ episode.number }} -
|
||||||
|
{{episode.name.slice(0, 40) + (episode.name.length > 39 ? '...' : '')}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!--LOADING SECTION-->
|
||||||
|
<div v-else-if="loading">
|
||||||
|
<p>Loading...</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
h3 {
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
.details-container {
|
.details-container {
|
||||||
padding-top: 10px;
|
padding-top: 10px;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@ -113,15 +156,62 @@ onMounted(async () => {
|
|||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.details-info {
|
|
||||||
display: flex;
|
|
||||||
gap: 1rem;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
|
|
||||||
.details-description {
|
.details-description {
|
||||||
padding-top: 10px;
|
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.episodes-container {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.episode-item {
|
||||||
|
background-color: #333;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.season-item {
|
||||||
|
background-color: #2a2a2a;
|
||||||
|
padding: 1rem;
|
||||||
|
margin-top: 5px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.season-item div {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.season-title {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: bold;
|
||||||
|
padding-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.episode-item:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.episode-title {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.episodes-container {
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.season-item div {
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
Loading…
x
Reference in New Issue
Block a user