django_rq removed (server too weak), loading spinner added during upload

This commit is contained in:
Chneemann 2024-08-18 18:59:53 +02:00
parent 9465ba7f18
commit 60309ac39c
12 changed files with 141 additions and 61 deletions

View file

@ -9,7 +9,6 @@ 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}'

View file

@ -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
queue.enqueue(delete_original_video, instance.video_file.path)
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):
"""

View file

@ -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.")

View file

@ -28,8 +28,4 @@ export class HeroBannerComponent {
playMovieId(videoPath: string) {
this.playMovie.emit(videoPath);
}
getImagePath(): any {
return '123';
}
}

View file

@ -92,3 +92,8 @@
</div>
</div>
</div>
@if(movieData.send){
<app-loading-dialog
loadingMsg="Movie is uploading, please be patient a moment."
></app-loading-dialog>
}

View file

@ -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));
}
}
}

View file

@ -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',
};

View file

@ -21,23 +21,10 @@ export class MovieService {
return lastValueFrom(this.http.get(url, { headers }));
}
uploadMovie(formData: FormData): Observable<any> {
uploadMovie(formData: FormData) {
const url = environment.baseUrl + '/content/upload/';
const headers = this.getAuthHeaders();
return this.http.post<any>(
`${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 {

View file

@ -0,0 +1,8 @@
<div class="loading-dialog">
<div class="loading-content">
<div id="loading" class="loader"></div>
<div id="loadingText" class="loading-text">
<p>{{ loadingMsg }}</p>
</div>
</div>
</div>

View file

@ -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);
}
}

View file

@ -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<LoadingDialogComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [LoadingDialogComponent]
})
.compileComponents();
fixture = TestBed.createComponent(LoadingDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View file

@ -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 = '';
}