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:
- Comprobamos si hay un vídeo que procesar.
- Creamos un directorio temporal.
- Definimos la ruta absoluta de la futura miniatura.
- Ejecutamos
ffmpeg
para crear la miniatura de la ruta donde esta guardado el vídeo a la ruta de la miniatura. - Guardamos la miniatura en el campo
video_thumbnail
. - 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.
{{ comments.length }} comentarios