π‘ Introduction
This article is a related to my previous article, in this I will discuss and share the code to make a gif maker of your own. If you want to know the idea behind this project do read my previous article.
π€ Why is it useful or what's the importance of this project?
Have you tried converting a video to a gif using an online tool? even a 5 sec gif can go upto 10 of MBs, and gifs can improve the overall look of the application by replacing the static images with a moving short glimpse of the video or reels.
ποΈ Architecture
Our current architecture for gif maker is, we have a consumer(RabbitMQ) written in python and when a video is uploaded and processes successfully, an event is fired with params "mp4Url" and "s3Destination", then the script downloads the video from the given url convert it to a compressed gif and upload it to the S3 destination.
π₯Έ Code and Explanation
from moviepy.editor import VideoFileClip
import os
import urllib.request
import uuid
import logging
import boto3
from pathlib import Path
from dotenv import dotenv_values
ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
config = dotenv_values(".env")
logging.basicConfig(
filename=str(Path(__file__).parents[2]) + "/logs/app.log",
format="%(name)s - %(levelname)s - %(message)s",
)
mp4UrlPrefix = config["CLOUDFRONT_VIDEO_PREFIX"]
def getGifUrlFromMp4Driver(params):
logging.info("Started getGifUrlFromMp4Driver")
if "mp4Url" not in params:
logging.error("mp4Url not in params")
return {"gifUrl": None}
if "s3Destination" not in params:
logging.error("s3Destination not in params")
return {"gifUrl": None}
# get full mp4 url
mp4Url = mp4UrlPrefix + "/" + params["mp4Url"]
s3Destination = params["s3Destination"]
# if tweaking params are not passed, then use the default
# value
fps = params.get("fps", 20)
fuzz = params.get("fuzz", 1)
start = params.get("start", 5)
duration = params.get("duration", 5)
videoWidth = params.get("videoWidth", 144)
videoHeight = params.get("videoHeight", 256)
try:
# initializing s3 session
session = boto3.Session(
aws_access_key_id=config["AWS_KEY"],
aws_secret_access_key=config["AWS_SECRET"],
)
s3 = session.resource("s3")
mp4_folder = ROOT_DIR + "/videos/"
gif_folder = ROOT_DIR + "/gifs/"
# creating a unique name for mp4 video download path
# and gif
name = str(uuid.uuid4())
# download mp4
downloadedVideoPath = f"{mp4_folder}{name}.mp4"
urllib.request.urlretrieve(mp4Url, downloadedVideoPath)
# to reduce size of gif as well as to not take a long
# time we will try 3 times with reduced frame rates
# and increased fuzz to reduce size of gif
counter = 0
convertedGifPath = f"{gif_folder}{name}.gif"
while True:
counter += 1
videoClip = VideoFileClip(downloadedVideoPath)
# take a clip of video from x to y
videoClip = videoClip.subclip(start, start + duration)
# resizing video dimensions to desired width and
# height, this also reduces gif size to choose it
# wisely
videoClip = videoClip.resize((videoWidth, videoHeight))
# setting video fps, this also reduces gif size
videoClip = videoClip.set_fps(fps)
videoClip.write_gif(
filename=convertedGifPath,
program="ImageMagick",
opt="optimizeplus",
tempfiles=True,
verbose=False,
fuzz=fuzz,
logger=None,
)
# get size of converted gif
file_size = os.path.getsize(convertedGifPath)
# greater than 500Kb then reduce fps
if file_size > 500000 and counter <= 3:
if counter == 1:
fps = 15
elif counter == 2:
fps = 10
elif counter == 3:
fps = 5
continue
break
# remove downloaded video from disk
os.remove(downloadedVideoPath)
destBucketName = config["AWS_BUCKET_IMAGE_NAME"]
if s3Destination[-1] != "/":
s3Destination += "/"
gifPath = "gif" + ".gif"
# upload gif to s3 bucket
s3.Bucket(destBucketName).upload_file(convertedGifPath,
s3Destination + gifPath)
gifUrl = f"{s3Destination}{gifPath}"
os.remove(convertedGifPath)
# return back the uploaded gif url
return {"gifUrl": gifUrl}
except Exception as e:
logging.error(f"Error in getGifUrlFromMp4Driver: {e}")
os.remove(downloadedVideoPath)
return {"gifUrl": None}
The above code is executed when the consumer listening to the event is triggered, here's an example of consumer
import json
from dotenv import dotenv_values
import logging
from getGifUrlFromMp4 import getGifUrlFromMp4Driver
import requests
config = dotenv_values(".env")
def gifConsumer(ch, method, properties, body):
try:
params = json.loads(body)
print(f" Received: {params}")
res = getGifUrlFromMp4Driver(params)
print(f" Gif created, url: {res}")
if res["gifUrl"] == None:
print("Gif url not found")
ch.basic_ack(delivery_tag=method.delivery_tag)
return
res["id"] = res["gifUrl"].split("/")[1]
mediaResponse = requests.post(
<url>, data=res
)
print(f"Response from media endpoint status code: {mediaResponse.status_code}")
print(f"Response from endpoint: {mediaResponse.json()}")
ch.basic_ack(delivery_tag=method.delivery_tag)
except Exception as e:
print("Error in gifConsumer: ", e)
logging.error(f"Error in gifConsumer: {e}")
ch.basic_ack(delivery_tag=method.delivery_tag)
This consumer calls getGifUrlFromMp4Driver
with mp4Url and s3Destination, the code is well explained in the comment, so do read them. After successfull or unsuccessfull conversion of gif controls comes back to the consumer function, now in case there's a gif url we give a api call to our endpoint informing it a successfull conversion of the gif and to save the gif url with the reel.
Here's the gist code in case formatting is messed up here: gist
Top comments (1)
great buddy