added task queue (django rq) & removes all files when the model is deleted
This commit is contained in:
parent
3564fcc360
commit
9e976e06fd
6 changed files with 90 additions and 24 deletions
|
|
@ -1,4 +1,18 @@
|
||||||
asgiref==3.8.1
|
asgiref==3.8.1
|
||||||
|
click==8.1.7
|
||||||
Django==5.0.7
|
Django==5.0.7
|
||||||
|
django-cors-headers==4.4.0
|
||||||
|
django-debug-toolbar==4.4.6
|
||||||
|
django-redis==5.4.0
|
||||||
|
django-rq==2.10.2
|
||||||
djangorestframework==3.15.2
|
djangorestframework==3.15.2
|
||||||
|
ffmpeg-python==0.2.0
|
||||||
|
future==1.0.0
|
||||||
|
imageio-ffmpeg==0.5.1
|
||||||
|
numpy==2.0.1
|
||||||
|
pillow==10.4.0
|
||||||
|
psutil==6.0.0
|
||||||
|
redis==5.0.8
|
||||||
|
rq==1.16.2
|
||||||
|
setuptools==72.1.0
|
||||||
sqlparse==0.5.1
|
sqlparse==0.5.1
|
||||||
|
|
@ -6,8 +6,8 @@ class Video(models.Model):
|
||||||
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)
|
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='img', null=True, blank=True)
|
thumbnail = models.ImageField(upload_to='thumbnails/', blank=True, null=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.title
|
return self.title
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
from .models import Video
|
from .models import Video
|
||||||
from content.tasks import convert_video, create_thumbnail
|
from content.tasks import convert_video, create_thumbnails
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.db.models.signals import post_save, post_delete
|
from django.db.models.signals import post_save, post_delete
|
||||||
|
from django.conf import settings
|
||||||
import os
|
import os
|
||||||
|
import django_rq
|
||||||
|
|
||||||
@receiver(post_save, sender=Video)
|
@receiver(post_save, sender=Video)
|
||||||
def video_post_save(sender, instance, created, **kwargs):
|
def video_post_save(sender, instance, created, **kwargs):
|
||||||
|
|
@ -10,20 +12,58 @@ def video_post_save(sender, instance, created, **kwargs):
|
||||||
Generates a thumbnail for a newly created `Video` instance.
|
Generates a thumbnail for a newly created `Video` instance.
|
||||||
"""
|
"""
|
||||||
if created:
|
if created:
|
||||||
|
queue = django_rq.get_queue("default", autocommit=True)
|
||||||
|
|
||||||
#Convert Video
|
#Convert Video
|
||||||
convert_video(instance.video_file.path, "480")
|
queue.enqueue(convert_video, instance.video_file.path, "480")
|
||||||
convert_video(instance.video_file.path, "720")
|
queue.enqueue(convert_video, instance.video_file.path, "720")
|
||||||
convert_video(instance.video_file.path, "1080")
|
queue.enqueue(convert_video, instance.video_file.path, "1080")
|
||||||
|
|
||||||
#Create Thumbnail
|
#Create Thumbnail
|
||||||
create_thumbnail(instance)
|
queue.enqueue(create_thumbnails, instance)
|
||||||
|
|
||||||
@receiver(post_delete, sender=Video)
|
@receiver(post_delete, sender=Video)
|
||||||
def auto_delete_file_on_delete(sender, instance, **kwargs):
|
def auto_delete_file_on_delete(sender, instance, **kwargs):
|
||||||
"""
|
"""
|
||||||
Deletes file from filesystem
|
Deletes video file and its converted files from filesystem
|
||||||
when corresponding `Video` object is deleted.
|
when corresponding `Video` object is deleted.
|
||||||
"""
|
"""
|
||||||
|
# Delete the original video file
|
||||||
if instance.video_file:
|
if instance.video_file:
|
||||||
if os.path.isfile(instance.video_file.path):
|
if os.path.isfile(instance.video_file.path):
|
||||||
os.remove(instance.video_file.path)
|
os.remove(instance.video_file.path)
|
||||||
|
|
||||||
|
# Delete the converted videos
|
||||||
|
if instance.video_file:
|
||||||
|
delete_converted_files(instance.video_file.path)
|
||||||
|
|
||||||
|
# Delete the thumbnails
|
||||||
|
if instance.thumbnail:
|
||||||
|
thumbnail_dir = settings.THUMBNAIL_DIR
|
||||||
|
base_filename = os.path.splitext(os.path.basename(instance.video_file.path))[0]
|
||||||
|
thumbnail_1080p_path = os.path.join(thumbnail_dir, base_filename + '_1080p.jpg')
|
||||||
|
thumbnail_480_path = os.path.join(thumbnail_dir, base_filename + '_480p.jpg')
|
||||||
|
|
||||||
|
if os.path.isfile(thumbnail_1080p_path):
|
||||||
|
os.remove(thumbnail_1080p_path)
|
||||||
|
if os.path.isfile(thumbnail_480_path):
|
||||||
|
os.remove(thumbnail_480_path)
|
||||||
|
|
||||||
|
def remove_first_mp4(filename):
|
||||||
|
"""
|
||||||
|
Remove the first .mp4 extension from the filename.
|
||||||
|
"""
|
||||||
|
name_part, ext_part = filename.split('.mp4', 1)
|
||||||
|
new_filename = name_part + ext_part
|
||||||
|
return new_filename
|
||||||
|
|
||||||
|
def delete_converted_files(video_file_path):
|
||||||
|
"""
|
||||||
|
Delete all converted video files related to the original video file.
|
||||||
|
"""
|
||||||
|
resolutions = ["480", "720", "1080"]
|
||||||
|
for resolution in resolutions:
|
||||||
|
converted_video_path = video_file_path + f'_{resolution}p.mp4'
|
||||||
|
converted_video_path = remove_first_mp4(converted_video_path)
|
||||||
|
if os.path.isfile(converted_video_path):
|
||||||
|
os.remove(converted_video_path)
|
||||||
|
|
|
||||||
|
|
@ -15,27 +15,27 @@ def remove_first_mp4(filename):
|
||||||
new_filename = name_part + ext_part
|
new_filename = name_part + ext_part
|
||||||
return new_filename
|
return new_filename
|
||||||
|
|
||||||
def create_thumbnail(source):
|
def create_thumbnails(self):
|
||||||
video_file_path = source.video_file.path
|
video_file_path = self.video_file.path
|
||||||
thumbnail_dir = settings.THUMBNAIL_DIR
|
thumbnail_dir = settings.THUMBNAIL_DIR
|
||||||
|
|
||||||
base_filename = os.path.splitext(os.path.basename(video_file_path))[0]
|
base_filename = os.path.splitext(os.path.basename(video_file_path))[0]
|
||||||
thumbnail_1920_filename = base_filename + '_1920p.jpg'
|
thumbnail_1080p_filename = base_filename + '_1080p.jpg'
|
||||||
thumbnail_1920_path = os.path.join(thumbnail_dir, thumbnail_1920_filename)
|
thumbnail_1080p_path = os.path.join(thumbnail_dir, thumbnail_1080p_filename)
|
||||||
thumbnail_480_filename = base_filename + '_480p.jpg'
|
thumbnail_480_filename = base_filename + '_480p.jpg'
|
||||||
thumbnail_480_path = os.path.join(thumbnail_dir, thumbnail_480_filename)
|
thumbnail_480_path = os.path.join(thumbnail_dir, thumbnail_480_filename)
|
||||||
|
thumbnail_path = os.path.join(thumbnail_dir, base_filename + ".jpg")
|
||||||
|
|
||||||
if not os.path.exists(thumbnail_dir):
|
if not os.path.exists(thumbnail_dir):
|
||||||
os.makedirs(thumbnail_dir)
|
os.makedirs(thumbnail_dir)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ffmpeg.input(video_file_path, ss=1).output(thumbnail_1920_path, vf='scale=1920:-1', vframes=1).run(overwrite_output=True)
|
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=480:-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)
|
||||||
|
|
||||||
source.thumbnail_1920 = os.path.relpath(thumbnail_1920_path, start=settings.MEDIA_ROOT)
|
# Hier speichern wir nur das Thumbnail für die Vorschau im Modell
|
||||||
source.thumbnail_480 = os.path.relpath(thumbnail_480_path, start=settings.MEDIA_ROOT)
|
self.thumbnail = os.path.relpath(thumbnail_path, start=settings.MEDIA_ROOT)
|
||||||
|
self.save()
|
||||||
source.save()
|
|
||||||
|
|
||||||
except ffmpeg._run.Error as e:
|
except ffmpeg._run.Error as e:
|
||||||
print(f"Ein Fehler ist aufgetreten: {e.stderr.decode()}")
|
print(f"Ein Fehler ist aufgetreten: {e.stderr.decode()}")
|
||||||
|
|
@ -52,6 +52,7 @@ INSTALLED_APPS = [
|
||||||
'rest_framework',
|
'rest_framework',
|
||||||
'corsheaders',
|
'corsheaders',
|
||||||
'debug_toolbar',
|
'debug_toolbar',
|
||||||
|
'django_rq',
|
||||||
'content.apps.ContentConfig',
|
'content.apps.ContentConfig',
|
||||||
'users',
|
'users',
|
||||||
]
|
]
|
||||||
|
|
@ -102,10 +103,20 @@ INTERNAL_IPS = [
|
||||||
'127.0.0.1',
|
'127.0.0.1',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
RQ_QUEUES = {
|
||||||
|
'default': {
|
||||||
|
'HOST': 'localhost',
|
||||||
|
'PORT': 6379,
|
||||||
|
'DB': 0,
|
||||||
|
'PASSWORD': 'foobared',
|
||||||
|
'DEFAULT_TIMEOUT': 360,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
CACHE_TTL = 60 * 15
|
CACHE_TTL = 60 * 15
|
||||||
|
|
||||||
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
|
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
|
||||||
THUMBNAIL_DIR = os.path.join(BASE_DIR, 'media/img')
|
THUMBNAIL_DIR = os.path.join(BASE_DIR, 'media/thumbnails')
|
||||||
MEDIA_URL = '/media/'
|
MEDIA_URL = '/media/'
|
||||||
|
|
||||||
WSGI_APPLICATION = 'videoflix.wsgi.application'
|
WSGI_APPLICATION = 'videoflix.wsgi.application'
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ 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
|
from django.urls import path, include
|
||||||
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 as user_views
|
from users import views as user_views
|
||||||
|
|
@ -32,6 +32,7 @@ from debug_toolbar.toolbar import debug_toolbar_urls
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
|
path('django-rq/', include('django_rq.urls')),
|
||||||
|
|
||||||
# Content URLs
|
# Content URLs
|
||||||
path('content/', content_views.video_list),
|
path('content/', content_views.video_list),
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue