feat: create database entry for new video only after successful upload
This commit is contained in:
parent
a2cfe78445
commit
91dc969c6d
7 changed files with 91 additions and 47 deletions
|
|
@ -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')
|
||||
|
|
@ -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)
|
||||
|
||||
41
backend/video_app/services.py
Normal file
41
backend/video_app/services.py
Normal 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
|
||||
)
|
||||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue