使用 imagekit 和 opencv 创建视频缩略图

Creating video thumbnails with imagekit and opencv

我有一个名为 "Post" 的模型,它将引用图像和视频。我为缩略图存储添加了 ImageSpecField 并创建了一个函数,它从上传的视频中提取所需的帧。生成缩略图时有什么方法可以使用此功能吗?因为现在 ImageSpecField 只能使用 FileField 作为输入。

我尝试创建一个新的 class 继承自 ImageSpecField,但我很快意识到这行不通,因为此 class 仅在服务器启动时实例化,因此将此函数放入它的构造函数将不起作用。

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

我希望 imagekit 从视频生成缩略图,并能够通过 ImageSpecField 获取它。

好的,我想我终于明白了。我设法通过创建另一个字段 - thumbnail_source_image 并根据上传的文件类型执行以下操作来实现它:

  • 对于图像 - 将 thumbnail_source_image 设置为与文件字段相同的值
  • 对于视频 - 从给定的毫秒视频生成图像,将其保存到与文件相同的位置,并将其设置为 thumbnail_source_image

我正在使用魔术库获取文件类型。

但是这种方法有一个小问题——要让 Django 生成文件路径,我们必须在我们的模型上调用 save() 方法。这迫使我们向数据库发出两个请求,而不是一个。

utils.py 文件:

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 文件:

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)