I have a model named "Post", which is going to refer to images and videos. I added ImageSpecField for thumbnail storage and created a function, that pulls the desired frame from uploaded video. Is there any way to use this function while generating thumbnail? Because right now ImageSpecField can only use FileField as an input.
I have tried creating a new class inheriting from ImageSpecField, but I quickly realized that this is not going to work because this class was instanced only on server start, thus putting this function in constructor of it would not work.
import cv2 as cv
from django.conf import settings
from django.db import models
from imagekit.processors import ResizeToFit
from imagekit.models import ImageSpecField
def video_to_image(source, frame):
vid_cap = cv.VideoCapture(settings.MEDIA_ROOT + source.__str__())
vid_cap.set(cv.CAP_PROP_POS_FRAMES, frame)
success, image = vid_cap.read()
vid_cap.release()
return image
class Post(models.Model):
IMAGE = 'I'
VIDEO = 'V'
FILE_TYPES = [
(IMAGE, 'Image'),
(VIDEO, 'Video')
]
file_type = models.CharField(max_length=1, choices=FILE_TYPES)
file = models.FileField(upload_to='post_images')
thumbnail_frame = models.IntegerField(default=0)
image_thumbnail = ImageSpecField(source='file',
processors=[ResizeToFit(width=200, height=200)],
format='JPEG',
options={'quality': 60})
I want imagekit to generate thumbnail from video, and be able to get it via ImageSpecField.
Ok, I think I finally got it. I managed to achieve it by creating another field - thumbnail_source_image and depending on the uploaded file type doing the following:
I am getting file type using the magic library.
But there is one small problem with this method - for Django to generate file path, we have to call save() method on our model. This forces us to make two requests to the database, instead of one.
import cv2 as cv
def save_frame_from_video(video_path, millisecond, frame_file_path):
vidcap = cv.VideoCapture(video_path)
vidcap.set(cv.CAP_PROP_POS_MSEC, millisecond)
success, image = vidcap.read()
# save image to temp file
cv.imwrite(frame_file_path, image)
vidcap.release()
import os
from django.conf import settings
from django.db import models
from imagekit.processors import ResizeToFit
from imagekit.models import ImageSpecField
from .utils import save_frame_from_video
class Post(models.Model):
image_types = ['image/jpeg', 'image/gif', 'image/png']
video_types = ['video/webm']
IMAGE = 'I'
VIDEO = 'V'
TYPES = [
(IMAGE, 'Image'),
(VIDEO, 'Video'),
]
type = models.CharField(max_length=1, choices=TYPES, blank=True)
file = models.FileField(upload_to='post_files/%Y/%m/%d/')
thumbnail_millisecond = models.IntegerField(default=0)
thumbnail_source_image = models.ImageField(upload_to='post_files/%Y/%m/%d/', null=True, blank=True)
image_thumbnail = ImageSpecField(source='thumbnail_source_image',
processors=[
ResizeToFit(150,
150,
mat_color=(230, 230, 230)),
],
format='JPEG',
options={'quality': 80})
def _set_type(self):
# max bytes to read for file type detection
read_size = 5 * (1024 * 1024) # 5MB
# read mime type of file
from magic import from_buffer
mime = from_buffer(self.file.read(read_size), mime=True)
if mime in self.image_types:
self.type = self.IMAGE
elif mime in self.video_types:
self.type = self.VIDEO
def _set_thumbnail_source_image(self):
if self.type == self.IMAGE:
self.thumbnail_source_image = self.file
elif self.type == self.VIDEO:
# create thumbnail source file
image_path = os.path.splitext(self.file.path)[0] + '_thumbnail_src_image.jpg'
save_frame_from_video(self.file.path, int(self.thumbnail_millisecond), image_path)
# generate path relative to media root, because this is the version that ImageField accepts
media_image_path = os.path.relpath(image_path, settings.MEDIA_ROOT)
self.thumbnail_source_image = media_image_path
def save(self, *args, **kwargs):
if self.type == '':
self._set_type()
# if there is no source image
if not bool(self.thumbnail_source_image):
# we need to save first, for django to generate path for file in "file" field
super().save(*args, **kwargs)
self._set_thumbnail_source_image()
super().save(*args, **kwargs)