Rud Ein
Rud Ein

Reputation: 73

Creating video thumbnails with imagekit and opencv

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.

Upvotes: 3

Views: 2157

Answers (1)

Rud Ein
Rud Ein

Reputation: 73

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:

  • for image - setting thumbnail_source_image to the same value as the file field
  • for video - generating an image from given millisecond of video, saving it to the same location as the file, and setting it as thumbnail_source_image

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.

utils.py file:

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()

models.py file:

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)

Upvotes: 1

Related Questions