switch to movie data from the backend
This commit is contained in:
parent
87b785d386
commit
817ddc63dd
12 changed files with 121 additions and 65 deletions
|
|
@ -5,8 +5,9 @@ class Video(models.Model):
|
||||||
created_at = models.DateField(default=date.today)
|
created_at = models.DateField(default=date.today)
|
||||||
title = models.CharField(max_length=80)
|
title = models.CharField(max_length=80)
|
||||||
description = models.CharField(max_length=500)
|
description = models.CharField(max_length=500)
|
||||||
|
film_genre = models.CharField(max_length=50, blank=True, null=True)
|
||||||
video_file = models.FileField(upload_to='videos', blank=True, null=True)
|
video_file = models.FileField(upload_to='videos', blank=True, null=True)
|
||||||
thumbnail = models.FileField(upload_to='thumbnails/', null=True, blank=True)
|
thumbnail = models.FileField(upload_to='img', null=True, blank=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.title
|
return self.title
|
||||||
7
backend/videoflix/content/serializer.py
Normal file
7
backend/videoflix/content/serializer.py
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
from rest_framework import serializers
|
||||||
|
from .models import Video
|
||||||
|
|
||||||
|
class VideoSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Video
|
||||||
|
fields = "__all__"
|
||||||
|
|
@ -22,7 +22,7 @@ def video_post_save(sender, instance, created, **kwargs):
|
||||||
os.makedirs(thumbnail_dir)
|
os.makedirs(thumbnail_dir)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ffmpeg.input(video_file_path, ss=1).output(absolute_thumbnail_path, vframes=1).run(overwrite_output=True)
|
ffmpeg.input(video_file_path, ss=1).output(absolute_thumbnail_path, vf='scale=300:-1', vframes=1).run(overwrite_output=True)
|
||||||
instance.thumbnail = os.path.relpath(thumbnail_path, start=settings.MEDIA_ROOT)
|
instance.thumbnail = os.path.relpath(thumbnail_path, start=settings.MEDIA_ROOT)
|
||||||
instance.save()
|
instance.save()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,16 @@
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
|
from rest_framework.decorators import api_view, permission_classes
|
||||||
|
from rest_framework.permissions import IsAuthenticated
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from .serializer import VideoSerializer
|
||||||
|
from .models import Video
|
||||||
|
|
||||||
# Create your views here.
|
# Create your views here.
|
||||||
|
@api_view(['GET'])
|
||||||
|
@permission_classes([IsAuthenticated])
|
||||||
|
def video_list(request):
|
||||||
|
|
||||||
|
if request.method == 'GET':
|
||||||
|
video = Video.objects.all()
|
||||||
|
serializer = VideoSerializer(video, many=True)
|
||||||
|
return Response(serializer.data)
|
||||||
|
|
@ -15,16 +15,31 @@ Including another URLconf
|
||||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||||
"""
|
"""
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.urls import path, include
|
from django.urls import path
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.conf.urls.static import static
|
from django.conf.urls.static import static
|
||||||
from users import views
|
from users import views as user_views
|
||||||
from auth.views import LoginView, RegisterView, VerifyEmailView, AuthView, ForgotPasswordView,ChangePasswordView
|
from auth.views import (
|
||||||
|
LoginView,
|
||||||
|
RegisterView,
|
||||||
|
VerifyEmailView,
|
||||||
|
AuthView,
|
||||||
|
ForgotPasswordView,
|
||||||
|
ChangePasswordView
|
||||||
|
)
|
||||||
|
from content import views as content_views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
path('users/', views.user_list),
|
|
||||||
path('users/<int:id>/', views.user_detail),
|
# Content URLs
|
||||||
|
path('content/', content_views.video_list),
|
||||||
|
|
||||||
|
# Users URLs
|
||||||
|
path('users/', user_views.user_list),
|
||||||
|
path('users/<int:id>/', user_views.user_detail),
|
||||||
|
|
||||||
|
# Authentication URLs
|
||||||
path('auth/', AuthView.as_view()),
|
path('auth/', AuthView.as_view()),
|
||||||
path('auth/login/', LoginView.as_view()),
|
path('auth/login/', LoginView.as_view()),
|
||||||
path('auth/register/', RegisterView.as_view()),
|
path('auth/register/', RegisterView.as_view()),
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
></app-hero-banner>
|
></app-hero-banner>
|
||||||
<app-categories
|
<app-categories
|
||||||
[movies]="movies"
|
[movies]="movies"
|
||||||
[currentMovie]="currentMovie[0].id"
|
[currentMovie]="currentMovie[0]?.id"
|
||||||
(currentMovieId)="currentMovieId($event)"
|
(currentMovieId)="currentMovieId($event)"
|
||||||
></app-categories>
|
></app-categories>
|
||||||
} @else {
|
} @else {
|
||||||
|
|
@ -21,10 +21,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<video width="100%" height="100%" controls autoplay>
|
<video width="100%" height="100%" controls autoplay>
|
||||||
<source
|
<source src="{{ playMovie }}" type="video/mp4" />
|
||||||
src="./../../../../assets/movies/{{ playMovie }}"
|
|
||||||
type="video/mp4"
|
|
||||||
/>
|
|
||||||
Your browser does not support the video tag.
|
Your browser does not support the video tag.
|
||||||
</video>
|
</video>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -3,47 +3,42 @@ import { HeaderComponent } from '../../../shared/components/header/header.compon
|
||||||
import { HeroBannerComponent } from './hero-banner/hero-banner.component';
|
import { HeroBannerComponent } from './hero-banner/hero-banner.component';
|
||||||
import { CategoriesComponent } from './categories/categories.component';
|
import { CategoriesComponent } from './categories/categories.component';
|
||||||
import { AuthService } from '../../../services/auth.service';
|
import { AuthService } from '../../../services/auth.service';
|
||||||
|
import { MovieService } from '../../../services/movie.service';
|
||||||
|
import { Observable, Subscription } from 'rxjs';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-browse',
|
selector: 'app-browse',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [HeaderComponent, HeroBannerComponent, CategoriesComponent],
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
HeaderComponent,
|
||||||
|
HeroBannerComponent,
|
||||||
|
CategoriesComponent,
|
||||||
|
],
|
||||||
templateUrl: './browse.component.html',
|
templateUrl: './browse.component.html',
|
||||||
styleUrl: './browse.component.scss',
|
styleUrl: './browse.component.scss',
|
||||||
})
|
})
|
||||||
export class BrowseComponent implements OnInit {
|
export class BrowseComponent implements OnInit {
|
||||||
movies = [
|
movies: any[] = [];
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
title: 'Our Galaxy',
|
|
||||||
description:
|
|
||||||
'Experience the fascinating journey through the infinite expanse of our galaxy. "Our Galaxy" offers a detailed insight into the breathtaking structures, stars and planets of our cosmic home. Through stunning visual effects and comprehensive explanations, this documentary guides viewers through the mysteries of the universe and reveals the beauty and complexity of outer space. Perfect for astronomy lovers and the curious who want to learn more about the wonders of the universe',
|
|
||||||
category: 'Documentary',
|
|
||||||
imgPath: 'galaxy.png',
|
|
||||||
videoPath: 'galaxy.mp4',
|
|
||||||
create: '08.03.2024',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
title: 'Star Trek',
|
|
||||||
description:
|
|
||||||
'Join Captain Kirk and the crew of the starship USS Enterprise as they embark on thrilling adventures across the universe. "Star Trek" is a legendary sci-fi saga that explores the final frontier, filled with action, exploration, and unforgettable encounters with alien species. This epic journey showcases the spirit of discovery and the unwavering courage of the Starfleet members as they confront the unknown and protect the galaxy from various threats.',
|
|
||||||
category: 'Science Fiction',
|
|
||||||
imgPath: 'star-trek.png',
|
|
||||||
videoPath: 'star-trek.mp4',
|
|
||||||
create: '08.02.2024',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
constructor(private authService: AuthService) {}
|
constructor(
|
||||||
|
private authService: AuthService,
|
||||||
|
private movieService: MovieService
|
||||||
|
) {}
|
||||||
|
|
||||||
currentMovie: any[] = [];
|
currentMovie: any[] = [];
|
||||||
playMovie: string = '';
|
playMovie: string = '';
|
||||||
|
|
||||||
ngOnInit(): void {
|
async ngOnInit() {
|
||||||
|
await this.loadAllMovies();
|
||||||
this.currentMovie.length === 0 ? this.loadRandomMovie() : null;
|
this.currentMovie.length === 0 ? this.loadRandomMovie() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async loadAllMovies() {
|
||||||
|
this.movies = await this.movieService.getAllMovies();
|
||||||
|
}
|
||||||
|
|
||||||
closeVideo(): void {
|
closeVideo(): void {
|
||||||
this.playMovie = '';
|
this.playMovie = '';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
<section class="hide-scrollbar">
|
<section *ngIf="movies.length > 0" class="hide-scrollbar">
|
||||||
<!-- Column for films of the last 7 days -->
|
<!-- Column for films of the last 7 days -->
|
||||||
<div class="category">
|
<div *ngIf="recentMovies().length > 0" class="category">
|
||||||
<p>Added in the last 7 days</p>
|
<p>Added in the last 7 days</p>
|
||||||
<div class="movies">
|
<div class="movies">
|
||||||
@for (movie of recentMovies(); track movie) {
|
@for (movie of recentMovies(); track movie) {
|
||||||
<div class="movie" (click)="openCurrentMovie(movie.id)">
|
<div class="movie" (click)="openCurrentMovie(movie.id)">
|
||||||
<img
|
<img
|
||||||
[ngClass]="{ selected: movie.id === currentMovie }"
|
[ngClass]="{ selected: movie.id === currentMovie }"
|
||||||
src="./../../../../../assets/movies/banner/{{ movie.imgPath }}"
|
src="{{ environmentBaseUrl + movie.thumbnail }}"
|
||||||
alt=""
|
alt=""
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -25,7 +25,7 @@
|
||||||
<div class="movie" (click)="openCurrentMovie(movie.id)">
|
<div class="movie" (click)="openCurrentMovie(movie.id)">
|
||||||
<img
|
<img
|
||||||
[ngClass]="{ selected: movie.id === currentMovie }"
|
[ngClass]="{ selected: movie.id === currentMovie }"
|
||||||
src="./../../../../../assets/movies/banner/{{ movie.imgPath }}"
|
src="{{ environmentBaseUrl + movie.thumbnail }}"
|
||||||
alt=""
|
alt=""
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||||
|
import { environment } from '../../../../environments/environment.development';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-categories',
|
selector: 'app-categories',
|
||||||
|
|
@ -13,6 +14,8 @@ export class CategoriesComponent {
|
||||||
@Input() currentMovie: number = 0;
|
@Input() currentMovie: number = 0;
|
||||||
@Output() currentMovieId = new EventEmitter<number>();
|
@Output() currentMovieId = new EventEmitter<number>();
|
||||||
|
|
||||||
|
environmentBaseUrl: string = environment.baseUrl.slice(0, -1);
|
||||||
|
|
||||||
filmGenres = [
|
filmGenres = [
|
||||||
'Action',
|
'Action',
|
||||||
'Adventure',
|
'Adventure',
|
||||||
|
|
@ -38,7 +41,7 @@ export class CategoriesComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
allMovies(filmGenre: string) {
|
allMovies(filmGenre: string) {
|
||||||
return this.movies.filter((movie) => movie.category === filmGenre);
|
return this.movies.filter((movie) => movie.film_genre === filmGenre);
|
||||||
}
|
}
|
||||||
|
|
||||||
recentMovies() {
|
recentMovies() {
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,21 @@
|
||||||
<section
|
<section
|
||||||
class="movie-banner"
|
class="movie-banner"
|
||||||
[style.backgroundImage]="'url(' + getImagePath() + ')'"
|
[ngStyle]="{
|
||||||
|
'background-image':
|
||||||
|
'url(' + environmentBaseUrl + currentMovie[0]?.thumbnail + ')'
|
||||||
|
}"
|
||||||
>
|
>
|
||||||
@if (currentMovie.length > 0) {
|
@if (currentMovie.length > 0) {
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="title">{{ currentMovie[0].title }}</div>
|
<div class="title">{{ currentMovie[0]?.title }}</div>
|
||||||
<div class="description">
|
<div class="description">
|
||||||
{{ currentMovie[0].description }}
|
{{ currentMovie[0]?.description }}
|
||||||
</div>
|
</div>
|
||||||
<app-btn-large
|
<app-btn-large
|
||||||
[value]="'Play'"
|
[value]="'Play'"
|
||||||
[imgPath]="'play'"
|
[imgPath]="'play'"
|
||||||
[imgReverse]="true"
|
[imgReverse]="true"
|
||||||
(click)="playMovieId(currentMovie[0].videoPath)"
|
(click)="playMovieId(environmentBaseUrl + currentMovie[0]?.video_file)"
|
||||||
></app-btn-large>
|
></app-btn-large>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ import {
|
||||||
Output,
|
Output,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { BtnLargeComponent } from '../../../../shared/components/btn-large/btn-large.component';
|
import { BtnLargeComponent } from '../../../../shared/components/btn-large/btn-large.component';
|
||||||
|
import { MovieService } from '../../../../services/movie.service';
|
||||||
|
import { environment } from '../../../../environments/environment.development';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-hero-banner',
|
selector: 'app-hero-banner',
|
||||||
|
|
@ -19,28 +21,15 @@ export class HeroBannerComponent {
|
||||||
@Input() currentMovie: any[] = [];
|
@Input() currentMovie: any[] = [];
|
||||||
@Output() playMovie = new EventEmitter<string>();
|
@Output() playMovie = new EventEmitter<string>();
|
||||||
|
|
||||||
constructor(private el: ElementRef) {}
|
environmentBaseUrl: string = environment.baseUrl.slice(0, -1);
|
||||||
|
|
||||||
ngAfterViewInit() {
|
constructor(private el: ElementRef, private movieService: MovieService) {}
|
||||||
this.truncateText();
|
|
||||||
}
|
|
||||||
|
|
||||||
playMovieId(videoPath: string) {
|
playMovieId(videoPath: string) {
|
||||||
this.playMovie.emit(videoPath);
|
this.playMovie.emit(videoPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
getImagePath(): string {
|
getImagePath(): any {
|
||||||
return `./../../../../../assets/movies/banner/${this.currentMovie[0]?.imgPath}`;
|
return '123';
|
||||||
}
|
|
||||||
|
|
||||||
truncateText() {
|
|
||||||
const descriptionEl = this.el.nativeElement.querySelector('.description');
|
|
||||||
const maxHeight = descriptionEl.offsetHeight;
|
|
||||||
let text = descriptionEl.innerText;
|
|
||||||
|
|
||||||
while (descriptionEl.scrollHeight > maxHeight) {
|
|
||||||
text = text.slice(0, -1);
|
|
||||||
descriptionEl.innerText = text + '...';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
33
frontend/src/app/services/movie.service.ts
Normal file
33
frontend/src/app/services/movie.service.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { lastValueFrom, Observable, of } from 'rxjs';
|
||||||
|
import { environment } from '../environments/environment.development';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class MovieService {
|
||||||
|
constructor(private http: HttpClient) {}
|
||||||
|
|
||||||
|
getAllMovies(): Promise<any> {
|
||||||
|
const url = environment.baseUrl + '/content/';
|
||||||
|
const headers = this.getAuthHeaders();
|
||||||
|
return lastValueFrom(this.http.get(url, { headers }));
|
||||||
|
}
|
||||||
|
|
||||||
|
getMovieFiles(videoUrl: number): Promise<any> {
|
||||||
|
const url = environment.baseUrl + `${videoUrl}`;
|
||||||
|
const headers = this.getAuthHeaders();
|
||||||
|
return lastValueFrom(this.http.get(url, { headers }));
|
||||||
|
}
|
||||||
|
|
||||||
|
private getAuthHeaders(): HttpHeaders {
|
||||||
|
let authToken = localStorage.getItem('authToken');
|
||||||
|
if (!authToken) {
|
||||||
|
authToken = sessionStorage.getItem('authToken');
|
||||||
|
}
|
||||||
|
return new HttpHeaders({
|
||||||
|
Authorization: `Token ${authToken}`, // Ensure this matches your backend
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue