From 60309ac39c57f27ec2848a81e5a5193389b34c90 Mon Sep 17 00:00:00 2001 From: Chneemann Date: Sun, 18 Aug 2024 18:59:53 +0200 Subject: [PATCH] django_rq removed (server too weak), loading spinner added during upload --- backend/videoflix/content/models.py | 3 +- backend/videoflix/content/signals.py | 25 ++++++-- backend/videoflix/content/tasks.py | 12 +--- .../hero-banner/hero-banner.component.ts | 4 -- .../upload-movie/upload-movie.component.html | 5 ++ .../upload-movie/upload-movie.component.ts | 29 +++------- frontend/src/app/environments/environment.ts | 4 +- frontend/src/app/services/movie.service.ts | 19 +----- .../loading-dialog.component.html | 8 +++ .../loading-dialog.component.scss | 58 +++++++++++++++++++ .../loading-dialog.component.spec.ts | 23 ++++++++ .../loading-dialog.component.ts | 12 ++++ 12 files changed, 141 insertions(+), 61 deletions(-) create mode 100644 frontend/src/app/shared/components/loading-dialog/loading-dialog.component.html create mode 100644 frontend/src/app/shared/components/loading-dialog/loading-dialog.component.scss create mode 100644 frontend/src/app/shared/components/loading-dialog/loading-dialog.component.spec.ts create mode 100644 frontend/src/app/shared/components/loading-dialog/loading-dialog.component.ts diff --git a/backend/videoflix/content/models.py b/backend/videoflix/content/models.py index b733a01..5fab122 100644 --- a/backend/videoflix/content/models.py +++ b/backend/videoflix/content/models.py @@ -9,8 +9,7 @@ class Video(models.Model): film_genre = models.CharField(max_length=20, choices=FILM_GENRES, blank=True, null=True) video_file = models.FileField(upload_to='videos/', blank=True, null=True) file_name = models.CharField(max_length=50, blank=True, null=True) - thumbnail_created = models.BooleanField(default=False) - + def __str__(self): return f'({self.id}) {self.title}' diff --git a/backend/videoflix/content/signals.py b/backend/videoflix/content/signals.py index 506b686..623d407 100644 --- a/backend/videoflix/content/signals.py +++ b/backend/videoflix/content/signals.py @@ -1,5 +1,5 @@ from .models import Video -from .tasks import convert_video_to_hls, create_thumbnails, delete_original_video, update_thumbnail_status +from .tasks import convert_video_to_hls, create_thumbnails, delete_original_video from django.dispatch import receiver from django.db.models.signals import post_save, post_delete from django.conf import settings @@ -13,18 +13,31 @@ def video_post_save(sender, instance, created, **kwargs): Generates a thumbnail for a newly created `Video` instance. """ if created: - queue = django_rq.get_queue("default", autocommit=True) - #Create thumbnail - queue.enqueue(create_thumbnails, instance, instance.id) + create_thumbnails(instance, instance.id) #Convert video for resolution in ["480", "720", "1080"]: - queue.enqueue(convert_video_to_hls, instance.video_file.path, resolution, instance.id) + convert_video_to_hls(instance.video_file.path, resolution, instance.id) # Delete the original video file + delete_original_video(instance.video_file.path) + + """ + # With Django RQ Worker (My server is too slow for it) + if created: + queue = django_rq.get_queue("default", autocommit=True) + + Create thumbnail + queue.enqueue(create_thumbnails, instance, instance.id) + + Convert video + for resolution in ["480", "720", "1080"]: + #queue.enqueue(convert_video_to_hls, instance.video_file.path, resolution, #instance.id) + + Delete the original video file queue.enqueue(delete_original_video, instance.video_file.path) - + """ @receiver(post_delete, sender=Video) def auto_delete_file_on_delete(sender, instance, **kwargs): """ diff --git a/backend/videoflix/content/tasks.py b/backend/videoflix/content/tasks.py index 6231f50..7ec0ed7 100644 --- a/backend/videoflix/content/tasks.py +++ b/backend/videoflix/content/tasks.py @@ -56,16 +56,6 @@ def create_thumbnails(instance, model_id): # Generate the thumbnails ffmpeg.input(video_file_path, ss=1).output(thumbnail_1080p_path, vf='scale=1920:-1', vframes=1).run(overwrite_output=True) ffmpeg.input(video_file_path, ss=1).output(thumbnail_480_path, vf='scale=720:-1', vframes=1).run(overwrite_output=True) - # Update thumbnail status - update_thumbnail_status(model_id) except ffmpeg._run.Error as e: - print(f"Ein Fehler ist aufgetreten: {e.stderr.decode()}") - -def update_thumbnail_status(video_id): - try: - video = Video.objects.get(id=video_id) - video.thumbnail_created = True - video.save() - except Video.DoesNotExist: - print(f"Video with id {video_id} does not exist.") \ No newline at end of file + print(f"Ein Fehler ist aufgetreten: {e.stderr.decode()}") \ No newline at end of file diff --git a/frontend/src/app/components/home/browse/hero-banner/hero-banner.component.ts b/frontend/src/app/components/home/browse/hero-banner/hero-banner.component.ts index d59f4e3..d822694 100644 --- a/frontend/src/app/components/home/browse/hero-banner/hero-banner.component.ts +++ b/frontend/src/app/components/home/browse/hero-banner/hero-banner.component.ts @@ -28,8 +28,4 @@ export class HeroBannerComponent { playMovieId(videoPath: string) { this.playMovie.emit(videoPath); } - - getImagePath(): any { - return '123'; - } } diff --git a/frontend/src/app/components/home/browse/upload-movie/upload-movie.component.html b/frontend/src/app/components/home/browse/upload-movie/upload-movie.component.html index cd1b49a..a2d0352 100644 --- a/frontend/src/app/components/home/browse/upload-movie/upload-movie.component.html +++ b/frontend/src/app/components/home/browse/upload-movie/upload-movie.component.html @@ -92,3 +92,8 @@ +@if(movieData.send){ + +} diff --git a/frontend/src/app/components/home/browse/upload-movie/upload-movie.component.ts b/frontend/src/app/components/home/browse/upload-movie/upload-movie.component.ts index 717516d..4d12e74 100644 --- a/frontend/src/app/components/home/browse/upload-movie/upload-movie.component.ts +++ b/frontend/src/app/components/home/browse/upload-movie/upload-movie.component.ts @@ -4,11 +4,17 @@ import { FormsModule, NgForm } from '@angular/forms'; import { ErrorService } from '../../../../services/error.service'; import { BtnLargeComponent } from '../../../../shared/components/buttons/btn-large/btn-large.component'; import { MovieService } from '../../../../services/movie.service'; +import { LoadingDialogComponent } from '../../../../shared/components/loading-dialog/loading-dialog.component'; @Component({ selector: 'app-upload-movie', standalone: true, - imports: [CommonModule, FormsModule, BtnLargeComponent], + imports: [ + CommonModule, + FormsModule, + BtnLargeComponent, + LoadingDialogComponent, + ], templateUrl: './upload-movie.component.html', styleUrl: './upload-movie.component.scss', }) @@ -48,15 +54,8 @@ export class UploadMovieComponent { try { this.movieData.send = true; - - const formData = this.createFormData(); - const response = await this.movieService - .uploadMovie(formData) - .toPromise(); - const videoId = response.id; - - await this.waitForThumbnailCreation(videoId); - + let formData = this.createFormData(); + await this.movieService.uploadMovie(formData); ngForm.resetForm(); this.uploadMovieOverview(); this.movieData.send = false; @@ -77,14 +76,4 @@ export class UploadMovieComponent { } return formData; } - - private async waitForThumbnailCreation(videoId: number) { - while (true) { - const statusResponse = await this.movieService - .checkThumbnailStatus(videoId) - .toPromise(); - if (statusResponse?.thumbnail_created) break; - await new Promise((resolve) => setTimeout(resolve, 2000)); - } - } } diff --git a/frontend/src/app/environments/environment.ts b/frontend/src/app/environments/environment.ts index eb946eb..81c24e7 100644 --- a/frontend/src/app/environments/environment.ts +++ b/frontend/src/app/environments/environment.ts @@ -1,7 +1,7 @@ export const environment = { // Development - // baseUrl: 'http://127.0.0.1:8000', + baseUrl: 'http://127.0.0.1:8000', // Live - baseUrl: 'https://videoflix-django.andre-kempf.com', + // baseUrl: 'https://videoflix-django.andre-kempf.com', }; diff --git a/frontend/src/app/services/movie.service.ts b/frontend/src/app/services/movie.service.ts index a2c12a6..9a74678 100644 --- a/frontend/src/app/services/movie.service.ts +++ b/frontend/src/app/services/movie.service.ts @@ -21,23 +21,10 @@ export class MovieService { return lastValueFrom(this.http.get(url, { headers })); } - uploadMovie(formData: FormData): Observable { + uploadMovie(formData: FormData) { + const url = environment.baseUrl + '/content/upload/'; const headers = this.getAuthHeaders(); - return this.http.post( - `${environment.baseUrl}/content/upload/`, - formData, - { headers } - ); - } - - checkThumbnailStatus( - videoId: number - ): Observable<{ thumbnail_created: boolean }> { - const headers = this.getAuthHeaders(); - return this.http.get<{ thumbnail_created: boolean }>( - `${environment.baseUrl}/content/${videoId}/status/`, - { headers } - ); + return lastValueFrom(this.http.post(url, formData, { headers })); } private getAuthHeaders(): HttpHeaders { diff --git a/frontend/src/app/shared/components/loading-dialog/loading-dialog.component.html b/frontend/src/app/shared/components/loading-dialog/loading-dialog.component.html new file mode 100644 index 0000000..2b811b5 --- /dev/null +++ b/frontend/src/app/shared/components/loading-dialog/loading-dialog.component.html @@ -0,0 +1,8 @@ +
+
+
+
+

{{ loadingMsg }}

+
+
+
diff --git a/frontend/src/app/shared/components/loading-dialog/loading-dialog.component.scss b/frontend/src/app/shared/components/loading-dialog/loading-dialog.component.scss new file mode 100644 index 0000000..7666235 --- /dev/null +++ b/frontend/src/app/shared/components/loading-dialog/loading-dialog.component.scss @@ -0,0 +1,58 @@ +@import "./../../../../assets/style/colors.scss"; + +.loading-dialog { + position: fixed; + top: 0; + bottom: 0; + left: 0; + right: 0; + width: 100vw; + height: 100vh; + backdrop-filter: blur(5px); + background-color: rgba($black, 0.2); + display: flex; + justify-content: center; + align-items: center; + z-index: 999; +} + +.loading-content { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +.loader { + border: 6px solid $white; + border-top: 6px solid $blue; + border-radius: 50%; + width: 60px; + height: 60px; + animation: spin 1.5s linear infinite; + box-shadow: 0px 0px 15px rgba($blue, 0.5); + margin-bottom: 20px; +} + +.loading-text { + text-align: center; + max-width: 300px; + padding: 12px; + color: $white; + text-shadow: 3px 3px 3px $black; + + p { + font-size: 21px; + font-weight: 400; + margin-bottom: 6px; + } +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} diff --git a/frontend/src/app/shared/components/loading-dialog/loading-dialog.component.spec.ts b/frontend/src/app/shared/components/loading-dialog/loading-dialog.component.spec.ts new file mode 100644 index 0000000..eb04690 --- /dev/null +++ b/frontend/src/app/shared/components/loading-dialog/loading-dialog.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { LoadingDialogComponent } from './loading-dialog.component'; + +describe('LoadingDialogComponent', () => { + let component: LoadingDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [LoadingDialogComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(LoadingDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/shared/components/loading-dialog/loading-dialog.component.ts b/frontend/src/app/shared/components/loading-dialog/loading-dialog.component.ts new file mode 100644 index 0000000..f10b965 --- /dev/null +++ b/frontend/src/app/shared/components/loading-dialog/loading-dialog.component.ts @@ -0,0 +1,12 @@ +import { Component, Input } from '@angular/core'; + +@Component({ + selector: 'app-loading-dialog', + standalone: true, + imports: [], + templateUrl: './loading-dialog.component.html', + styleUrl: './loading-dialog.component.scss', +}) +export class LoadingDialogComponent { + @Input() loadingMsg: string = ''; +}