Let's Build: gerador de GIF de disco girando
Logo depois de entrar para a equipe de suporte da Transloadit, enquanto me familiarizava com os nossos serviços, encontrei uma pergunta no Stack Overflow que despertou meu interesse, porque me permitia unir meu amor pela música à nossa API. A pergunta era se os nossos serviços poderiam ser usados para gerar uma animação de um disco girando, e achei a ideia interessante o bastante para virar um post no blog.

Este artigo aborda várias práticas interessantes que você pode querer usar nos seus projetos e mostra a versatilidade do serviço da Transloadit. Vamos ver conceitos como usar o nosso SDK para Python com bibliotecas externas para combinar codificação interna e externa, combinar dois Templates de forma assíncrona e usar o FFmpeg para ampliar os recursos da nossa API. Então, sem mais delongas, vamos começar!
Pré-requisitos
Além do SDK para Python já mencionado, precisamos baixar algumas outras ferramentas para o projeto de hoje. Vamos usar o OpenCV para fazer cálculos bit a bit, junto com o NumPy, que ajuda no processamento local de imagens. Os dois pacotes serão instalados com o instalador de pacotes pip.
Além dos pacotes, precisamos de alguns arquivos de imagem, que você pode baixar nos links abaixo:
Vamos dividir o código em partes para explicar tudo neste post, mas, se você quiser baixar o projeto completo, confira este repositório no GitHub.
Configurando o projeto
Precisamos de uma pasta de projeto com um novo arquivo Python e uma pasta Assets/. Coloque as imagens
baixadas em Assets/, com a capa do álbum nomeada como okcomputer.jpg e o fundo do disco como vinyl.png.
Agora podemos instalar todos os pacotes necessários. Para isso, execute os seguintes comandos no
seu terminal:
python3 -m venv .venv
source .venv/bin/activate
pip install pytransloadit
pip install numpy
pip install opencv-python
pip install requests
pip install future
Crie um novo arquivo Python e importe os nossos pacotes.
from __future__ import print_function
from transloadit import client
import os
import requests
import cv2
import numpy as np
Em seguida, crie uma instância do cliente da Transloadit.
tl = client.Transloadit(os.environ['TRANSLOADIT_KEY'], os.environ['TRANSLOADIT_SECRET'])
Defina essas duas variáveis de ambiente com a Auth Key e o Auth Secret do seu Workspace. Execute o script localmente ou em um servidor confiável; não distribua o segredo para usuários finais.
Para terminar, vamos criar um conjunto de variáveis globais para usar mais tarde. Confira se
img_name corresponde ao nome da imagem do álbum e se vinyl_path corresponde ao nome da imagem do disco de vinil.
img_name = 'okcomputer'
img_path = 'Assets/{name}.jpg'.format(name=img_name)
vinyl_path = 'Assets/vinyl.png'
remove_bg_location = 'Assets/trimmed_image.png'
Agora está tudo pronto para criarmos o nosso primeiro Template!
Templates
Acesse o Transloadit Console e crie um Template em branco. Não se esqueça de anotar o seu Template ID para mais tarde. Abaixo, copiamos o código do nosso Template, que é simplesmente a receita em JSON para criar a sua mágica com a Transloadit. Vamos passar por cada Step abaixo e explicar o que está acontecendo.
{
"steps": {
":original": {
"robot": "/upload/handle"
},
"resize": {
"use": ":original",
"robot": "/image/resize",
"format": "png",
"resize_strategy": "fillcrop",
"width": 350,
"height": 350,
"imagemagick_stack": "v3.0.1"
}
}
}
O nosso primeiro Step, :original, cuida do upload do arquivo de entrada. Se você quiser
enviar um arquivo de um local de terceiros, confira aqui todos os nossos
serviços compatíveis.
Em seguida, resize recorta a imagem e define o formato como PNG. Isso é necessário para garantir
que a imagem fique na escala correta sobre o disco de vinil no final.
Usando nossos Templates
Como estamos usando vários Templates, precisamos criar um pequeno script em Python para conectá-los entre si. Faremos isso analisando o resultado da Assembly do nosso primeiro Template e, em seguida, pegando o valor da URL temporária resultante e passando-o para o nosso segundo Template.
A função abaixo recebe o Template ID que anotamos antes, o caminho do arquivo da imagem e
o nome do último Step do nosso Template (no nosso exemplo, seria
resize). Ela também tem alguns parâmetros opcionais, como se queremos ou não retornar a URL
e, o mais importante, fields. Com os campos, podemos passar variáveis para o nosso
Template e assim alterar os parâmetros do Template direto do nosso 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
Também precisamos criar mais uma função utilitária para baixar a imagem redimensionada para a pasta
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)
Removendo o fundo
Este Template é simples: ele ativa o canal alfa da nossa imagem PNG e depois torna transparentes todos os pixels pretos. Por outro lado, isso também significa que precisamos ter cuidado ao usar capas de álbum com fundo preto, como Dark Side of the Moon, do Pink Floyd.
{
"steps": {
":original": {
"robot": "/upload/handle"
},
"trimmed": {
"use": ":original",
"robot": "/image/resize",
"alpha": "Activate",
"type": "TrueColor",
"transparent": "0,0,0",
"imagemagick_stack": "v3.0.1"
}
}
}
Sobrepondo a imagem
Agora, um pouco de mágica com campos✨. Vamos pegar a URL da imagem da nossa última Assembly e passá-la como um campo para o nosso Template. O Robot /image/resize vai usar a imagem como marca-d’água para sobrepô-la ao nosso disco de vinil.
{
"steps": {
":original": {
"robot": "/upload/handle"
},
"watermark": {
"use": ":original",
"robot": "/image/resize",
"watermark_url": "${fields.url}",
"watermark_size": "33%",
"watermark_position": "center",
"imagemagick_stack": "v3.0.1"
}
}
}
Transformando tudo em GIF!
Este é o nosso último Template. Ele pode parecer intimidador, mas é bem simples quando você o analisa por partes.
{
"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"
}
}
}
}
O primeiro Step, import, serve para, bem, importar o nosso arquivo. Usamos o
Robot /http/import para não precisar baixar localmente a
imagem da Assembly anterior. Para isso, é necessária uma URL de onde importar. Felizmente,
podemos usar facilmente o campo que criamos para passar uma URL a partir do nosso script em Python.
É aqui que usamos o FFmpeg para criar o efeito de rotação que queremos. Usamos a opção de vídeo
rotate na imagem sobreposta, definindo o ângulo, em radianos, como pi multiplicado pelo tempo
decorrido t, em segundos. Isso resulta em uma volta completa a cada dois segundos. c=white
preenche com branco o fundo que fica exposto. A opção de saída do GIF loop=0 faz a animação
final se repetir indefinidamente; ela é independente do filtro de vídeo.
Juntando tudo em Python
Agora devemos ter todas as peças necessárias para criar o nosso GIF de vinil girando!
Vamos começar redimensionando a imagem e baixando-a localmente:
resize_url = useTemplate('RESIZE_IMAGE_TEMPLATE_ID', img_path, 'resize')
resized_image_location = 'Assets/resized_image.png'
downloadImage(resize_url, resized_image_location)
Em seguida, precisamos criar a nossa função de máscara de imagem. Vamos chamá-la de maskImage e passar
a ela um 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
O início da função cria uma imagem preta com exatamente as mesmas dimensões da capa do álbum e depois cria dois círculos brancos para formar uma rosquinha, assim:

Agora fazemos uma operação AND bit a bit entre a máscara e a imagem original. Isso significa que um pixel da imagem original só aparece onde a máscara tem um pixel branco, o que nos dá o seguinte resultado:

Por fim, usamos o Template anterior para tornar os pixels pretos transparentes.

Chamamos a função assim:
trimmed_url = maskImage(resized_image_location)
Agora precisamos colocar a nossa rosquinha sobre o disco de vinil e fazê-lo girar! Vamos aplicar a imagem como marca-d’água usando o Template de antes:
vinyl_url = useTemplate('WATERMARK_IMAGE_TEMPLATE_ID', vinyl_path, 'watermark', True, {'url': trimmed_url})

Agora, ao passar a imagem estática para o nosso Template gerador de GIF junto com uma taxa de quadros e uma duração, que definimos no nosso script, podemos fazê-la 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 fim, podemos baixar o resultado localmente.
final_gif_location = 'Assets/finished_gif.gif'
downloadImage(final_gif_url, final_gif_location)
E aí está, finalmente: o nosso próprio disco de vinil animado girando!🎉

Espero que você tenha curtido o processo e talvez até aprendido algumas coisas sobre a API da Transloadit que possa usar no seu próximo projeto! E, se quiser levar este projeto ainda mais longe, que tal transformá-lo em um MP4 e adicionar uma música para acompanhar o disco, ou usar o Uppy para permitir que os usuários enviem as próprias imagens para colocar em um disco de vinil girando?
