feat: create database entry for new video only after successful upload

This commit is contained in:
Chneemann 2025-05-10 13:34:13 +02:00
parent a2cfe78445
commit 91dc969c6d
7 changed files with 91 additions and 47 deletions

View file

@ -11,4 +11,4 @@ class VideoResource(resources.ModelResource):
class VideoAdmin(ImportExportModelAdmin):
resource_class = VideoResource
readonly_fields = ('file_path',)
list_display = ('title', 'file_path', 'created_at')
list_display = ('title', 'file_path', 'genre', 'created_at')

View file

@ -2,7 +2,7 @@ from django.db import models
from django.conf import settings
from datetime import date
from .class_assets import VIDEO_GENRES
import os
import uuid
class Video(models.Model):
creator = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE,default=1)
@ -15,11 +15,3 @@ class Video(models.Model):
def __str__(self):
return f'({self.id}) {self.title}'
def save(self, *args, **kwargs):
if self.file_path and not self.file_name:
file_path = self.file_path.path
base_filename, _ = os.path.splitext(os.path.basename(file_path))
self.file_name = base_filename
super().save(*args, **kwargs)

View file

@ -0,0 +1,41 @@
import os
import uuid
from django.core.files.storage import FileSystemStorage
from django.conf import settings
from .models import Video
def validate_video_file(uploaded_file, max_size_mb=20):
"""
Validate a video file
"""
if not uploaded_file:
return 'No file uploaded'
if not uploaded_file.name.endswith('.mp4'):
return 'Invalid file type. Only .mp4 files are allowed.'
if uploaded_file.size > max_size_mb * 1024 * 1024:
return f'File size exceeds the allowed limit of {max_size_mb}MB'
return None
def save_video_file(uploaded_file):
"""
Save an uploaded video file and return the short name and the file path.
"""
short_name = uuid.uuid4().hex[:6]
ext = os.path.splitext(uploaded_file.name)[1]
new_filename = f"{short_name}{ext}"
fs = FileSystemStorage(location=settings.VIDEO_DIR)
fs.save(new_filename, uploaded_file)
return short_name, os.path.join('videos', new_filename)
def create_video_record(data, user, short_name, file_path):
"""
Creates a Video record in the database.
"""
return Video.objects.create(
title=data.get('title'),
description=data.get('description'),
genre=data.get('genre'),
creator=user,
file_name=short_name,
file_path=file_path
)

View file

@ -9,20 +9,27 @@ import shutil
@receiver(post_save, sender=Video)
def video_post_save(sender, instance, created, **kwargs):
"""
Generates a thumbnail, enqueues tasks to convert the video to different resolutions, and schedules the deletion of the original video file.
Signal handler for processing a video after saving:
- Create thumbnails
- Convert video to different resolutions (HLS)
- Delete original video
"""
if created:
queue = django_rq.get_queue("default", autocommit=True)
if not created:
return
#Create thumbnail
create_thumbnails(instance, instance.id)
if instance.file_path and instance.file_name:
try:
create_thumbnails(instance, instance.id)
#Convert video
for resolution in ["1280x720", "640x360", "1920x1080"]:
queue.enqueue(convert_video_to_hls, instance.file_path.path, resolution, instance.id)
queue = django_rq.get_queue("default", autocommit=True)
resolutions = ["1280x720", "640x360", "1920x1080"]
for resolution in resolutions:
queue.enqueue(convert_video_to_hls, instance.file_path.path, resolution, instance.id)
#Delete the original video file
queue.enqueue(delete_original_video, instance.file_path.path)
queue.enqueue(delete_original_video, instance.file_path.path)
except Exception as e:
print(f"Video ID video processing error {instance.id}: {e}")
@receiver(post_delete, sender=Video)
def auto_delete_file_on_delete(sender, instance, **kwargs):

View file

@ -10,7 +10,7 @@ def convert_video_to_hls(source, resolution, model_id):
"""
resolution_after_x = resolution.split('x')[1] if 'x' in resolution else resolution
target_dir = os.path.join(os.path.dirname(source), str(model_id))
target_dir = os.path.join(settings.VIDEO_DIR, str(model_id))
os.makedirs(target_dir, exist_ok=True)
base_filename = os.path.basename(source).split(".")[0]

View file

@ -1,25 +1,21 @@
from django.forms import ValidationError
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
from .services import validate_video_file, save_video_file, create_video_record
from .class_assets import VIDEO_GENRES
from django.core.cache.backends.base import DEFAULT_TIMEOUT
from django.views.decorators.cache import cache_page
from django.conf import settings
from rest_framework.parsers import MultiPartParser, FormParser
from django.http import JsonResponse
from django.db import transaction
import os
CACHE_TTL = getattr(settings, 'CACHE_TTL', DEFAULT_TIMEOUT)
# Create your views here.
@api_view(['GET'])
@permission_classes([IsAuthenticated])
#@cache_page(CACHE_TTL)
def video_list(request):
"""
List all videos.
List all videos
"""
videos = Video.objects.all()
serializer = VideoSerializer(videos, many=True)
@ -61,13 +57,21 @@ def check_video_resolutions(request, id):
@permission_classes([IsAuthenticated])
def video_upload(request):
"""
Handle the upload of a video file.
Handles the upload of a video file:
"""
parser_classes = (MultiPartParser, FormParser)
if request.method == 'POST':
serializer = VideoSerializer(data=request.data)
if serializer.is_valid():
creator = request.user
serializer.save(creator=creator)
return Response(serializer.data, status=201)
return Response({"error": "Invalid data", "details": serializer.errors}, status=400)
uploaded_file = request.FILES.get('file_path')
error = validate_video_file(uploaded_file)
if error:
return Response({'error': error}, status=400)
try:
short_name, file_path = save_video_file(uploaded_file)
with transaction.atomic():
video = create_video_record(request.data, request.user, short_name, file_path)
return Response(VideoSerializer(video).data, status=201)
except ValidationError as ve:
return Response({'error': f'Validation Error: {str(ve)}'}, status=400)
except Exception as e:
return Response({'error': f'Error while saving video file: {str(e)}'}, status=500)

View file

@ -107,8 +107,6 @@ RQ_QUEUES = {
}
}
CACHE_TTL = 60 * 15
WSGI_APPLICATION = 'videoflix.wsgi.application'
# Media
@ -117,7 +115,9 @@ MEDIA_ROOT = BASE_DIR / 'media'
MEDIA_URL = '/media/'
THUMBNAIL_DIR = MEDIA_ROOT / 'thumbnails'
THUMBNAIL_DIR = str(MEDIA_ROOT / 'thumbnails')
VIDEO_DIR = str(MEDIA_ROOT / 'videos')
# Database
# https://docs.djangoproject.com/en/5.0/ref/settings/#databases