Crear previa o imagen miniatura de video en Django | Programador Web Valencia

Crear previa o imagen miniatura de video en Django

3 minutos

Django

Cuando se sube un vídeo a un servidor, es común que se quiera mostrar una previa. Es útil para enseñar un vistazo rápido del vídeo antes de reproducirlo. En HTML se puede utilizar el atributo poster de la etiqueta video. Por ello en este artículo te voy a enseñar como crearla automáticamente.

Si solo buscas una forma de crear una miniatura de una imagen, puedes visitar el siguiente artículo.

Como requisito previo, necesitas tener instalado ffmpeg en tu sistema. Si no es así, puedes instalarlo con el siguiente comando con Debian o derivado (Ubuntu, Mint, etc):

sudo apt-get install ffmpeg

En otro sistemas operativos, debes buscar la forma de instalarlo. Escapa de mis conocimientos.

Empezaremos añadiendo en settings.py una variable llamada DOMAIN_URL que contendrá la URL de tu dominio.

DOMAIN_URL = "https://www.tudominio.com"

Continuamos. Para el ejemplo utilizaré un modelo llamado Publication.

from django.db import models

class Publication(models.Model):
    video_thumbnail = models.ImageField(upload_to=videos/thumbnails', blank=True, null=True)
    video = models.FileField(upload_to='videos/', blank=True, null=True)
    pub_date = models.DateTimeField(default=timezone.now)
  • video_thumbnail guardará la futura miniatura del vídeo.
  • video guardará el vídeo original.
  • pub_date guardará la fecha de publicación. Totalmente opcional.

Añadimos un método llamado make_thumbnail_video que se encargará de crear la miniatura del vídeo.

import os
import subprocess
import tempfile
import shutil

class Publication(models.Model):

    ...
    def make_thumbnail_video(self):
        if self.video:
            temp_dir = tempfile.mkdtemp()
            temp_file = os.path.join(temp_dir, 'thumbnail.jpg')
            subprocess.run(['ffmpeg', '-i', self.video.path, '-ss', '00:00:01.000', '-vframes', '1', temp_file])
            self.video_thumbnail.save('thumbnail.jpg', open(temp_file, 'rb'))
            shutil.rmtree(temp_dir)

Los pasos que siguen son:

  1. Comprobamos si hay un vídeo que procesar.
  2. Creamos un directorio temporal.
  3. Definimos la ruta absoluta de la futura miniatura.
  4. Ejecutamos ffmpeg para crear la miniatura de la ruta donde esta guardado el vídeo a la ruta de la miniatura.
  5. Guardamos la miniatura en el campo video_thumbnail.
  6. Eliminamos el directorio temporal.

Pero esta función debe ejecutarse en algún momento. Las señales son una buena opción.

from django.db.models.signals import post_save
from django.dispatch import receiver

@receiver(post_save, sender=Publication)
def make_video_thumbnail(sender, instance, **kwargs):
    post_save.disconnect(make_video_thumbnail, sender=Publication)
    instance.make_thumbnail_video()
    post_save.connect(make_video_thumbnail, sender=Publication)

Cada vez que se guarde una publicación, se ejecutará la función make_video_thumbnail.

Como expliqué en el artículo de Crear miniaturas de imágenes en Django, para evitar un bucle infinito, desconectamos la señal antes de ejecutar la función y la volvemos a conectar después. La forma de hacerlo es llamando a disconnect y connect de la señal post_save, dejando tu código en el medio.

El detalle final sería incluir una property que devuelva la URL de la miniatura del vídeo.

from django.conf import settings

class Publication

        ...
        @property
        def public_video_url_thumbnail(self):
            if not self.video_thumbnail:
                self.make_thumbnail_video()
            return settings.DOMAIN_URL + self.video_thumbnail.url

Hemos incluido otra automatización. Si no hay una miniatura, se creará automáticamente. A continuación, se devolverá la URL de la miniatura.

El código completo sería el siguiente:

import os
from django.db import models
from django.conf import settings
from django.utils import timezone
from django.db.models.signals import post_save
from django.dispatch import receiver
import subprocess
import tempfile
import shutil



class Publication(models.Model):
    video_thumbnail = models.ImageField(upload_to=videos/thumbnails', blank=True, null=True)
    video = models.FileField(upload_to='videos/', blank=True, null=True)
    pub_date = models.DateTimeField(default=timezone.now)

    def make_thumbnail_video(self):
        if self.video:
            temp_dir = tempfile.mkdtemp()
            temp_file = os.path.join(temp_dir, 'thumbnail.jpg')
            subprocess.run(['ffmpeg', '-i', self.video.path, '-ss', '00:00:01.000', '-vframes', '1', temp_file])
            self.video_thumbnail.save('thumbnail.jpg', open(temp_file, 'rb'))
            shutil.rmtree(temp_dir)

    @property
    def public_video_url_thumbnail(self):
        if not self.video_thumbnail:
            self.make_thumbnail_video()
        return settings.DOMAIN_URL + self.video_thumbnail.url


@receiver(post_save, sender=Publication)
def make_video_thumbnail(sender, instance, **kwargs):
    post_save.disconnect(make_video_thumbnail, sender=Publication)
    instance.make_thumbnail_video()
    post_save.connect(make_video_thumbnail, sender=Publication)

Para terminar ejecutamos las migraciones.

python manage.py makemigrations
python manage.py migrate

Y con esto ya habríamos terminado. Ahora cada vez que se suba un vídeo, se creará una miniatura sin que nosotros debamos hacer nada.

No es perfecto, hay algunas pegas relacionadas con la naturaleza y gran cantidad de recursos que se necesitan para procesar vídeos.

  • El servidor necesitará muchos recursos para procesar vídeos. Aunque sea para tan solo crear una miniatura.
  • Habrá 2 retardos: uno al subir el vídeo y otro al crear la miniatura. Puede ser un problema si el vídeo es muy largo.
  • Siempre que se guarda una publicación, se creará la miniatura. Deberías detectar cuando el vídeo ha cambiado o no.

También sería recomendado utilizar una cola de tareas como Celery para procesar vídeos. Pero eso ya es otro tema. Tampoco es mala idea usar la extensión django-cleanup para eliminar las miniaturas que ya no se usan.

Espero que te haya sido útil y te sirva de base para tus proyectos.

Si te ha gustado, puedes compartirlo en tus redes sociales. Si tienes alguna duda, puedes dejar un comentario.

Esta obra está bajo una Licencia Creative Commons Atribución-NoComercial-SinDerivadas 4.0 Internacional.

Atribución/Reconocimiento-NoComercial-SinDerivados 4.0 Internacional

¿Me invitas a un café? ☕

Puedes hacerlo usando el terminal.

ssh customer@andros.dev -p 5555

Comentarios

{{ comments.length }} comentarios

Nuevo comentario

Nueva replica  {{ formatEllipsisAuthor(replyComment.author) }}

Acepto la política de Protección de Datos.

Escribe el primer comentario

Tal vez también te interese...