Let's Build: generador de GIF de disco giratorio
Como miembro recién contratado del equipo de soporte de Transloadit, estaba familiarizándome con nuestros servicios cuando encontré una pregunta en Stack Overflow que despertó mi interés, ya que me permitía combinar mi amor por la música con nuestra API. La pregunta era si nuestros servicios se podían usar para generar una animación de un disco girando, y me pareció que la idea era lo bastante interesante como para convertirla en una entrada de blog.

Este artículo cubre varias prácticas interesantes que quizá quieras usar en tus proyectos y muestra la versatilidad del servicio de Transloadit. Veremos conceptos como el uso de nuestro Python SDK con bibliotecas externas para combinar encoding interno y externo, la combinación asíncrona de dos Templates y el uso de FFmpeg para ampliar las capacidades de nuestra API. Así que, sin más preámbulos, ¡manos a la obra!
Requisitos previos
Además del Python SDK ya mencionado, hoy necesitamos descargar algunas otras herramientas para nuestro proyecto. Usaremos OpenCV para realizar cálculos bit a bit, junto con NumPy para ayudar con el procesamiento local de imágenes. Ambos paquetes se instalarán con el instalador de paquetes pip.
Junto con nuestros paquetes, necesitamos algunos recursos, que puedes descargar en el enlace de abajo:
Dividiremos el código en fragmentos para explicar mejor las cosas en este blog, pero si quieres descargar el proyecto completo, consulta el siguiente repositorio de GitHub.
Configuración del proyecto
Necesitamos una carpeta de proyecto que contenga un nuevo archivo de Python y una carpeta
Assets/. Coloca las imágenes descargadas en Assets/,
nombrando la carátula del álbum okcomputer.jpg y el fondo del disco
vinyl.png. Ahora podemos instalar todos los paquetes necesarios. Para ello,
ejecuta los siguientes comandos en tu consola:
python3 -m venv .venv
source .venv/bin/activate
pip install pytransloadit
pip install numpy
pip install opencv-python
pip install requests
pip install future
Crea un nuevo archivo de Python e importa nuestros paquetes.
from __future__ import print_function
from transloadit import client
import os
import requests
import cv2
import numpy as np
A continuación, crea una instancia del cliente de Transloadit.
tl = client.Transloadit(os.environ['TRANSLOADIT_KEY'], os.environ['TRANSLOADIT_SECRET'])
Asigna a esas dos variables de entorno la Auth Key y el Auth Secret de tu Workspace. Ejecuta el script localmente o en un servidor de confianza; no distribuyas el secreto a los usuarios finales.
Para terminar, creemos un conjunto de variables globales para usarlas más adelante. Asegúrate de que
img_name coincida con el nombre de la imagen del álbum y de que
vinyl_path coincida con el nombre de la imagen del disco de vinilo.
img_name = 'okcomputer'
img_path = 'Assets/{name}.jpg'.format(name=img_name)
vinyl_path = 'Assets/vinyl.png'
remove_bg_location = 'Assets/trimmed_image.png'
¡Ahora ya estamos listos para crear nuestro primer Template!
Templates
Dirígete a tu Transloadit Console y crea un Template en blanco. Asegúrate de anotar tu Template ID para más adelante. Abajo hemos copiado el código de nuestro Template, que no es más que la receta JSON para crear tu magia de Transloadit. Repasaremos cada Step a continuación y explicaremos qué ocurre.
{
"steps": {
":original": {
"robot": "/upload/handle"
},
"resize": {
"use": ":original",
"robot": "/image/resize",
"format": "png",
"resize_strategy": "fillcrop",
"width": 350,
"height": 350,
"imagemagick_stack": "v3"
}
}
}
Nuestro primer Step :original se encarga de subir nuestro archivo de entrada.
Si quieres subir un archivo desde una ubicación de terceros, puedes consultar aquí todos nuestros
servicios compatibles.
A continuación, resize se encarga de recortar nuestra imagen y establece el formato en PNG. Esto
es necesario para garantizar que al final la imagen se escale correctamente sobre el disco de vinilo.
Uso de nuestros Templates
Como usamos varios Templates, necesitamos crear un pequeño script de Python para enlazarlos entre sí. Lo conseguiremos analizando el resultado de la Assembly de nuestro primer Template, tomando después el valor de URL temporal resultante y pasándolo a nuestro segundo Template.
A la función de abajo se le pasan el Template ID que anotamos antes, la ruta del archivo de
nuestra imagen y el nombre del último Step de nuestro Template (en nuestro ejemplo sería
resize). También contiene algunos parámetros opcionales, como si queremos o no devolver la URL
y, lo más importante, fields. Con los campos podemos pasar variables a nuestro Template
para poder cambiar los parámetros de nuestro Template directamente desde nuestro cliente 👌
def useTemplate(templateID, file_path='', result_name='', get_url=True, fields=None):
assembly = tl.new_assembly({'template_id': templateID, 'fields': fields or {}})
if file_path:
with open(file_path, 'rb') as upload:
assembly.add_file(upload)
assembly_response = assembly.create(retries=5, wait=True)
else:
assembly_response = assembly.create(retries=5, wait=True)
if get_url:
if assembly_response.data.get('ok') != 'ASSEMBLY_COMPLETED':
raise RuntimeError('Assembly did not complete successfully')
assembly_url = assembly_response.data.get('results').get(result_name)[0].get('ssl_url')
return assembly_url
También necesitamos crear una función de utilidad más para descargar nuestra imagen redimensionada a
la carpeta Assets/.
def downloadImage(url, location):
r = requests.get(url, timeout=30)
r.raise_for_status()
with open(location, 'wb') as image:
image.write(r.content)
Eliminar el fondo
Este Template es sencillo: habilita el canal alfa en nuestra imagen PNG y luego convierte todos los píxeles negros en transparentes. Sin embargo, esto también significa que debemos tener cuidado cuando usamos una carátula de álbum con fondo negro, como la de Dark Side of the Moon de Pink Floyd.
{
"steps": {
":original": {
"robot": "/upload/handle"
},
"trimmed": {
"use": ":original",
"robot": "/image/resize",
"alpha": "Activate",
"type": "TrueColor",
"transparent": "0,0,0",
"imagemagick_stack": "v3"
}
}
}
Superponer la imagen
Ahora, un poco de magia con los campos✨. Vamos a tomar la URL de la imagen de nuestra última Assembly y pasarla como campo a nuestro Template. El Robot /image/resize usará la imagen como marca de agua para superponerla sobre nuestro disco de vinilo.
{
"steps": {
":original": {
"robot": "/upload/handle"
},
"watermark": {
"use": ":original",
"robot": "/image/resize",
"watermark_url": "${fields.url}",
"watermark_size": "33%",
"watermark_position": "center",
"imagemagick_stack": "v3"
}
}
}
¡Convertirlo en un GIF!
Este es nuestro último Template. Puede parecer intimidante, pero es bastante simple cuando lo desglosas.
{
"steps": {
"import": {
"robot": "/http/import",
"url": "${fields.url}",
"result": true
},
"animated": {
"robot": "/video/merge",
"use": "import",
"result": true,
"duration": "${fields.duration}",
"framerate": "${fields.framerate}",
"ffmpeg_stack": "v7",
"ffmpeg": {
"vf": "rotate=PI*t:c=white",
"f": "gif",
"pix_fmt": "rgb24",
"loop": "0"
}
}
}
}
El primer Step import se usa para, bueno, importar nuestro archivo. Usamos nuestro
Robot /http/import para evitar descargar localmente la imagen
de la Assembly anterior. Esto requiere una URL desde la que importar. Por suerte, podemos usar
fácilmente el campo que creamos para pasar una URL desde nuestro script de Python.
Ahora bien, en este punto usamos FFmpeg para crear el efecto giratorio que buscamos. Aplicamos el
flag de video rotate sobre la imagen superpuesta y fijamos el ángulo en radianes como pi multiplicado
por el tiempo transcurrido t en segundos. Eso da una vuelta completa cada dos segundos.
c=white rellena de blanco el fondo que queda expuesto. La opción de salida GIF
loop=0 hace que la animación terminada se repita indefinidamente; es independiente del filtro
de video.
Uniéndolo todo en Python
¡Ahora deberíamos tener todas las piezas necesarias para crear nuestro GIF de vinilo giratorio!
Empecemos por redimensionar la imagen y descargarla localmente:
resize_url = useTemplate('RESIZE_IMAGE_TEMPLATE_ID', img_path, 'resize')
resized_image_location = 'Assets/resized_image.png'
downloadImage(resize_url, resized_image_location)
A continuación, necesitamos crear nuestra función de enmascarado de imágenes. Llamémosla maskImage y
démosle un img_path.
def maskImage(img_path):
img = cv2.imread(img_path)
mask = np.zeros(img.shape, dtype=np.uint8)
mask = cv2.circle(mask, (175, 175), 175, (255, 255, 255), -1)
mask = cv2.circle(mask, (175, 175), 20, (0, 0, 0), -1)
result = cv2.bitwise_and(img, mask)
result_location = 'Assets/mask.png'
cv2.imwrite(result_location, result)
remove_bg_url = useTemplate('REMOVING_BG_TEMPLATE_ID', result_location, 'trimmed')
downloadImage(remove_bg_url, remove_bg_location)
return remove_bg_url
El principio de nuestra función crea una imagen negra con exactamente las mismas dimensiones que nuestra carátula del álbum y después crea dos círculos blancos para formar una rosquilla, así:

Ahora realizamos una operación AND bit a bit sobre la máscara y nuestra imagen original, lo que significa que un píxel de la imagen original solo se muestra en los lugares donde la máscara tiene un píxel blanco, lo que nos da el siguiente resultado:

Por último, usamos nuestro Template anterior para volver transparentes los píxeles negros.

Llamamos a nuestra función así:
trimmed_url = maskImage(resized_image_location)
¡Ahora tenemos que poner nuestra rosquilla sobre el disco de vinilo y hacerla girar! Apliquemos la marca de agua a la imagen usando nuestro Template de antes:
vinyl_url = useTemplate('WATERMARK_IMAGE_TEMPLATE_ID', vinyl_path, 'watermark', True, {'url': trimmed_url})

Ahora, al pasar la imagen fija a nuestro Template generador de GIF junto con una velocidad de fotogramas y una duración, que definimos desde nuestro script, ¡podemos hacerla girar!
frame_rate = 60
length = 2
final_gif_url = useTemplate('ROTATING_GIF_TEMPLATE_ID',
result_name='animated',
get_url=True,
fields={'url': vinyl_url, 'duration': length, 'framerate': frame_rate})
Por último, podemos descargar el resultado localmente.
final_gif_location = 'Assets/finished_gif.gif'
downloadImage(final_gif_url, final_gif_location)
Y ahí lo tenemos, por fin: ¡nuestro propio disco de vinilo giratorio animado!🎉

¡Espero que hayas disfrutado el proceso de crear esto y que incluso hayas aprendido algunas cosas sobre la API de Transloadit que puedas usar en tu próxima aventura! Y si quieres llevar este proyecto aún más lejos, ¿por qué no convertirlo en un MP4 y añadir algo de música que acompañe al disco, o usar Uppy para que los usuarios suban sus propias imágenes y las coloquen sobre un disco de vinilo giratorio?
