Let's Build: GIF-Generator für rotierende Schallplatten
Als frisch eingestelltes Mitglied des Transloadit-Supportteams habe ich mich gerade mit unseren Diensten vertraut gemacht, als ich auf eine Stack-Overflow-Frage stieß, die mein Interesse weckte, weil sie es mir ermöglichte, meine Liebe zur Musik mit unserer API zu verbinden. Die Frage war, ob sich mit unseren Diensten eine Animation einer rotierenden Schallplatte erzeugen lässt, und ich fand diese Idee interessant genug für einen Blogbeitrag.

Dieser Artikel behandelt einige spannende Verfahren, die Sie vielleicht in Ihren eigenen Projekten einsetzen möchten, und zeigt die Vielseitigkeit des Transloadit-Dienstes. Wir behandeln Konzepte wie die Verwendung unseres Python SDK zusammen mit externen Bibliotheken, um internes und externes Encoding zu kombinieren, das asynchrone Kombinieren zweier Templates und die Nutzung von FFmpeg, um die Möglichkeiten unserer API zu erweitern. Also genug der Vorrede, legen wir direkt los!
Voraussetzungen
Neben unserem bereits erwähnten Python SDK müssen wir für unser heutiges Projekt noch einige weitere Werkzeuge herunterladen. Wir verwenden OpenCV für bitweise Berechnungen, zusammen mit NumPy als Unterstützung bei der lokalen Bildverarbeitung. Beide Pakete werden mit dem Paket-Installationsprogramm pip installiert.
Zusätzlich zu unseren Paketen benötigen wir einige Assets, die Sie unter dem folgenden Link herunterladen können:
Wir teilen den Code in diesem Blog zur besseren Erklärung in einzelne Abschnitte auf. Wenn Sie das vollständige Projekt herunterladen möchten, sehen Sie sich bitte das folgende GitHub-Repo an.
Das Projekt einrichten
Wir benötigen einen Projektordner, der eine neue Python-Datei und einen Ordner namens
Assets/ enthält. Legen Sie die heruntergeladenen Bilder in
Assets/ ab und benennen Sie das Albumcover okcomputer.jpg
und den Schallplatten-Hintergrund vinyl.png. Jetzt können wir alle nötigen
Pakete installieren. Führen Sie dazu die folgenden Befehle in Ihrer Konsole aus:
python3 -m venv .venv
source .venv/bin/activate
pip install pytransloadit
pip install numpy
pip install opencv-python
pip install requests
pip install future
Erstellen Sie eine neue Python-Datei und importieren Sie unsere Pakete.
from __future__ import print_function
from transloadit import client
import os
import requests
import cv2
import numpy as np
Erstellen Sie als Nächstes eine Instanz des Transloadit-Clients.
tl = client.Transloadit(os.environ['TRANSLOADIT_KEY'], os.environ['TRANSLOADIT_SECRET'])
Setzen Sie diese beiden Umgebungsvariablen auf den Auth Key und das Auth Secret Ihres Workspaces. Führen Sie das Skript lokal oder auf einem vertrauenswürdigen Server aus; geben Sie das Secret nicht an Endnutzer weiter.
Zum Abschluss erstellen wir noch einige globale Variablen für die spätere Verwendung. Achten Sie
darauf, dass img_name mit dem Namen des Albumbilds übereinstimmt und
vinyl_path mit dem Namen des Schallplattenbilds.
img_name = 'okcomputer'
img_path = 'Assets/{name}.jpg'.format(name=img_name)
vinyl_path = 'Assets/vinyl.png'
remove_bg_location = 'Assets/trimmed_image.png'
Jetzt können wir unser erstes Template erstellen!
Templates
Gehen Sie in Ihre Transloadit-Konsole und erstellen Sie ein leeres Template. Notieren Sie sich unbedingt Ihre Template ID für später. Nachfolgend haben wir den Code unseres Templates eingefügt, der schlicht das JSON-Rezept für Ihre Transloadit-Magie ist. Wir gehen unten jeden Step durch und erklären, was dabei passiert.
{
"steps": {
":original": {
"robot": "/upload/handle"
},
"resize": {
"use": ":original",
"robot": "/image/resize",
"format": "png",
"resize_strategy": "fillcrop",
"width": 350,
"height": 350,
"imagemagick_stack": "v3"
}
}
}
Unser erster Step :original
übernimmt den Upload unserer Eingabedatei. Wenn Sie eine Datei von einem Drittanbieter-Speicherort
hochladen möchten, finden Sie hier alle unsere
unterstützten Dienste.
Als Nächstes übernimmt resize das Zuschneiden unseres Bildes und setzt das
Format auf PNG. Das ist nötig, damit das Bild am Ende korrekt auf der Schallplatte skaliert wird.
Unsere Templates verwenden
Da wir mehrere Templates verwenden, müssen wir ein kleines Python-Skript schreiben, das sie miteinander verknüpft. Dazu werten wir das Assembly-Ergebnis unseres ersten Templates aus, nehmen den daraus entstandenen temporären URL-Wert und übergeben ihn an unser zweites Template.
Der folgenden Funktion übergeben wir die zuvor notierte
Template ID, den Dateipfad unseres Bildes und den Namen
des letzten Steps in unserem
Template (in unserem Beispiel wäre das
resize). Sie enthält außerdem einige optionale Parameter, etwa ob wir die
URL zurückgeben möchten, und vor allem fields. Mit Feldern können wir
Variablen an unser Template übergeben, sodass wir die
Parameter unseres Templates direkt von unserem Client aus
ändern können 👌
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
Außerdem brauchen wir noch eine Hilfsfunktion, die unser in der Größe angepasstes Bild in den
Ordner Assets/ herunterlädt.
def downloadImage(url, location):
r = requests.get(url, timeout=30)
r.raise_for_status()
with open(location, 'wb') as image:
image.write(r.content)
Den Hintergrund entfernen
Dieses Template ist unkompliziert; es aktiviert den Alphakanal unseres PNG-Bildes und setzt anschließend alle schwarzen Pixel auf transparent. Das bedeutet allerdings auch, dass wir vorsichtig sein müssen, wenn wir ein Albumcover mit schwarzem Hintergrund verwenden, etwa Dark Side of the Moon von Pink Floyd.
{
"steps": {
":original": {
"robot": "/upload/handle"
},
"trimmed": {
"use": ":original",
"robot": "/image/resize",
"alpha": "Activate",
"type": "TrueColor",
"transparent": "0,0,0",
"imagemagick_stack": "v3"
}
}
}
Das Bild überlagern
Jetzt kommt etwas Feld-Magie✨. Wir nehmen die URL des Bildes aus unserer letzten Assembly und übergeben sie als Feld an unser Template. Der Robot /image/resize verwendet das Bild als Wasserzeichen und legt es über unsere Schallplatte.
{
"steps": {
":original": {
"robot": "/upload/handle"
},
"watermark": {
"use": ":original",
"robot": "/image/resize",
"watermark_url": "${fields.url}",
"watermark_size": "33%",
"watermark_position": "center",
"imagemagick_stack": "v3"
}
}
}
Daraus ein GIF machen!
Das ist unser letztes Template. Es sieht vielleicht einschüchternd aus, ist aber recht einfach, wenn man es in seine Einzelteile zerlegt.
{
"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"
}
}
}
}
Der erste Step import dient
dazu, nun ja, unsere Datei zu importieren. Wir verwenden unseren
Robot /http/import, um unser
Bild aus der vorherigen Assembly nicht lokal herunterladen
zu müssen. Dafür ist eine URL nötig, von der importiert wird. Glücklicherweise können wir dafür
einfach das Feld nutzen, das wir angelegt haben, um eine URL aus unserem Python-Skript zu
übergeben.
An dieser Stelle nutzen wir nun FFmpeg, um den gewünschten Rotationseffekt zu erzeugen. Wir wenden
das Video-Flag rotate auf das überlagerte Bild an und setzen den Winkel im Bogenmaß auf Pi
multipliziert mit der verstrichenen Zeit t in Sekunden. Das ergibt
eine volle Umdrehung alle zwei Sekunden. c=white füllt den freigelegten
Hintergrund mit Weiß. Die GIF-Ausgabeoption loop=0 sorgt dafür, dass sich
die fertige Animation endlos wiederholt; sie ist unabhängig vom Videofilter.
Alles in Python zusammenführen
Jetzt sollten wir alle Bausteine haben, die wir für unser GIF mit rotierender Schallplatte brauchen!
Beginnen wir damit, die Größe des Bildes zu ändern und es lokal herunterzuladen:
resize_url = useTemplate('RESIZE_IMAGE_TEMPLATE_ID', img_path, 'resize')
resized_image_location = 'Assets/resized_image.png'
downloadImage(resize_url, resized_image_location)
Als Nächstes brauchen wir unsere Funktion zum Maskieren des Bildes. Nennen wir sie
maskImage und übergeben ihr den Dateipfad 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
Zu Beginn erzeugt unsere Funktion ein schwarzes Bild mit genau denselben Abmessungen wie unser Albumcover und zeichnet dann zwei weiße Kreise, die einen Donut ergeben, so wie hier:

Nun führen wir eine bitweise UND-Verknüpfung von Maske und Originalbild durch. Das bedeutet, dass ein Pixel des Originalbilds nur an den Stellen sichtbar ist, an denen die Maske ein weißes Pixel hat. Damit erhalten wir folgendes Ergebnis:

Zum Schluss verwenden wir unser früheres Template, um die schwarzen Pixel transparent zu machen.

Wir rufen unsere Funktion so auf:
trimmed_url = maskImage(resized_image_location)
Jetzt müssen wir unseren Donut auf die Schallplatte legen und ihn zum Rotieren bringen! Versehen wir das Bild mithilfe unseres früheren Templates mit einem Wasserzeichen:
vinyl_url = useTemplate('WATERMARK_IMAGE_TEMPLATE_ID', vinyl_path, 'watermark', True, {'url': trimmed_url})

Indem wir das Standbild zusammen mit einer Bildrate und einer Länge, die wir in unserem Skript festlegen, an unser Template zur GIF-Erzeugung übergeben, können wir es nun zum Rotieren bringen!
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})
Zum Schluss können wir das Ergebnis lokal herunterladen.
final_gif_location = 'Assets/finished_gif.gif'
downloadImage(final_gif_url, final_gif_location)
Und da haben wir es endlich: unsere ganz eigene animierte rotierende Schallplatte!🎉

Ich hoffe, Sie hatten Freude daran, das nachzubauen, und haben vielleicht sogar einiges über die Transloadit API gelernt, das Sie bei Ihrem nächsten Vorhaben einsetzen können! Und wenn Sie dieses Projekt noch weitertreiben möchten: Warum nicht ein MP4 daraus machen und passende Musik zur Schallplatte ergänzen oder Uppy verwenden, damit Nutzer ihre eigenen Bilder hochladen und auf eine rotierende Schallplatte legen können?
