Converting MP4 to Animated GIFs with Python

Python can be used to read in the common MP4 video format and convert it to an animated GIF. Of course, you can use a pre-built piece of software if you'd rather, but it's fun (and a good learning experience) to do it yourself.

In this tutorial, you will learn the following:

  • How to extract frames from an MP4 video
  • Turn the frames into a GIF
  • Create an MP4 to GIF GUI

Let's get started!

What You Need

You will need to install the OpenCV bindings for Python to read in an MP4 file and convert each frame in the video into a JPG file. You can install that using pip like this:

python3 -m pip install opencv-python

You will also need Pillow to create the animated GIF from the JPGs that you extract from the video. It can also be installed with pip:

python3 -m pip install Pillow

To create a GUI, you will be using PySimpleGUI. To install that package, use the following command:

python3 -m pip install PySimpleGUI

If you are using Anaconda, opencv-python and Pillow are included. You will only need to install PySimpleGUI separately.

How to Extract Frames from an MP4 video

The first step to extracting frames from an MP4 video is to find a video that you want to convert to a GIF. For this example, you will use this short video that demonstrates how to install the Flask web framework with Python:

To extract the individual frames from the video above, you will need to write some Python. Create a new file and name it mp4_converter.py. Then enter the following code:

import cv2


def convert_mp4_to_jpgs(path):
    video_capture = cv2.VideoCapture(path)
    still_reading, image = video_capture.read()
    frame_count = 0
    while still_reading:
        cv2.imwrite(f"output/frame_{frame_count:03d}.jpg", image)
        
        # read next image
        still_reading, image = video_capture.read()
        frame_count += 1


if __name__ == "__main__":
    convert_mp4_to_jpgs("flask_demo.mp4")

This code takes the path to an MP4 video file. Then it opens the video using cv2.VideoCapture(path). You can use this method to read through the entire video and extract each frame. As you extract a frame, you can write it out using cv2.imwrite().

When you run this code, you will find that this 7-second video produces 235 frames!

Now you are ready to take those frames and turn them into an animated GIF.

Turn the Frames into a GIF

The next step in the process is to convert the frames that you extracted from the MP4 file using OpenCV into an animated GIF.

This is where the Pillow package comes in. You can use it to take in a folder of images and create your GIF. Open up a new file and name it gif_maker.py. Then enter the following code:

import glob

from PIL import Image


def make_gif(frame_folder):
    images = glob.glob(f"{frame_folder}/*.jpg")
    images.sort()
    frames = [Image.open(image) for image in images]
    frame_one = frames[0]
    frame_one.save("flask_demo.gif", format="GIF", append_images=frames,
                   save_all=True, duration=50, loop=0)
    

if __name__ == "__main__":
    make_gif("output")

Here you use Python's glob module to search the output folder for JPG files. Then you sort the frames so that they are in the right order. Finally, you save them as a GIF. If you're interested in learning more about how Pillow saves GIFs, you should check out the following article: Creating an Animated GIF with Python.

Now you are ready to create a GUI to streamline the process of converting an MP4 to a GIF.

Create an MP4 to GIF GUI

PySimpleGUI is a cross-platform GUI framework that runs on Linux, Mac and Windows. It wraps Tkinter, wxPython, PyQt and several other GUI toolkits, giving them all a common interface.

When you installed PySimpleGUI earlier in this article, you installed the default version which wraps Tkinter.

Open up a new Python file and name it mp4_converter_gui.py. Then add this code to your file:

# mp4_converter_gui.py

import cv2
import glob
import os
import shutil
import PySimpleGUI as sg

from PIL import Image

file_types = [("MP4 (*.mp4)", "*.mp4"), ("All files (*.*)", "*.*")]


def convert_mp4_to_jpgs(path):
    video_capture = cv2.VideoCapture(path)
    still_reading, image = video_capture.read()
    frame_count = 0
    if os.path.exists("output"):
        # remove previous GIF frame files
        shutil.rmtree("output")
    try:
        os.mkdir("output")
    except IOError:
        sg.popup("Error occurred creating output folder")
        return
    
    while still_reading:
        cv2.imwrite(f"output/frame_{frame_count:05d}.jpg", image)
        
        # read next image
        still_reading, image = video_capture.read()
        frame_count += 1


def make_gif(gif_path, frame_folder="output"):
    images = glob.glob(f"{frame_folder}/*.jpg")
    images.sort()
    frames = [Image.open(image) for image in images]
    frame_one = frames[0]
    frame_one.save(gif_path, format="GIF", append_images=frames,
                   save_all=True, duration=50, loop=0)


def main():
    layout = [
        [
            sg.Text("MP4 File"),
            sg.Input(size=(25, 1), key="-FILENAME-", disabled=True),
            sg.FileBrowse(file_types=file_types),
        ],
        [
            sg.Text("GIF File Save Location"),
            sg.Input(size=(25, 1), key="-OUTPUTFILE-", disabled=True),
            sg.SaveAs(file_types=file_types),
            
        ],
        [sg.Button("Convert to GIF")],
    ]

    window = sg.Window("MP4 to GIF Converter", layout)

    while True:
        event, values = window.read()
        mp4_path = values["-FILENAME-"]
        gif_path = values["-OUTPUTFILE-"]
        if event == "Exit" or event == sg.WIN_CLOSED:
            break
        if event in ["Convert to GIF"]:
            if mp4_path and gif_path:
                convert_mp4_to_jpgs(mp4_path)
                make_gif(gif_path)
                sg.popup(f"GIF created: {gif_path}")

    window.close()


if __name__ == "__main__":
    main()

This is a fairly lengthy chunk of code. To make things easier, you will learn about each chunk individually.

To get things started, take a look at the import section:

# mp4_converter_gui.py

import cv2
import glob
import os
import shutil
import PySimpleGUI as sg

from PIL import Image

file_types = [("MP4 (*.mp4)", "*.mp4"), ("All files (*.*)", "*.*")]

Here you import all the modules and packages you will need to create your GUI application. This includes OpenCV (cv2), Pillow's Image clas and PySimpleGUI, as well as a number of Python's own modules.

You also create a variable that holds the file types that you can load into your GUI. This is a list of tuples.

Now it's time to turn your attention to the first function in your program:

def convert_mp4_to_jpgs(path):
    video_capture = cv2.VideoCapture(path)
    still_reading, image = video_capture.read()
    frame_count = 0
    if os.path.exists("output"):
        # remove previous GIF frame files
        shutil.rmtree("output")
    try:
        os.mkdir("output")
    except IOError:
        sg.popup("Error occurred creating output folder")
        return
    
    while still_reading:
        cv2.imwrite(f"output/frame_{frame_count:05d}.jpg", image)
        
        # read next image
        still_reading, image = video_capture.read()
        frame_count += 1

This is a modified version of your MP4 converter code that you created earlier. In this version, you still use VideoCapture() to read the MP4 file and turn it into individual frames.

However, you have also added some extra code to remove the "output" folder if it exists. This prevents you from accidentally combining two MP4 files in one output file, which would make for a confusing GIF.

You also add some code to try to create the "output" folder after it was removed. If there is an error while creating the folder, an error dialog is shown.

The rest of the code is the same as before.

Now you're ready to check out the next function:

def make_gif(gif_path, frame_folder="output"):
    images = glob.glob(f"{frame_folder}/*.jpg")
    images.sort()
    frames = [Image.open(image) for image in images]
    frame_one = frames[0]
    frame_one.save(gif_path, format="GIF", append_images=frames,
                   save_all=True, duration=50, loop=0)

You use make_gif() to turn your folder of frames into a GIF file. This code is nearly identical to the original code except that you pass in the path of the GIF file so that it can be unique.

The last piece of code is your GUI code:

def main():
    layout = [
        [
            sg.Text("MP4 File"),
            sg.Input(size=(25, 1), key="-FILENAME-", disabled=True),
            sg.FileBrowse(file_types=file_types),
        ],
        [
            sg.Text("GIF File Save Location"),
            sg.Input(size=(25, 1), key="-OUTPUTFILE-", disabled=True),
            sg.SaveAs(file_types=file_types),
            
        ],
        [sg.Button("Convert to GIF")],
    ]

    window = sg.Window("MP4 to GIF Converter", layout)

    while True:
        event, values = window.read()
        mp4_path = values["-FILENAME-"]
        gif_path = values["-OUTPUTFILE-"]
        if event == "Exit" or event == sg.WIN_CLOSED:
            break
        if event in ["Convert to GIF"]:
            if mp4_path and gif_path:
                convert_mp4_to_jpgs(mp4_path)
                make_gif(gif_path)
                sg.popup(f"GIF created: {gif_path}")

    window.close()


if __name__ == "__main__":
    main()

In PySimpleGUI, when you want to "layout" the Elements in your user interface, you add the items to a Python list. For this example, you add the following Elements:

  • sg.Text - There are two instances of this Element. They are used as labels for the Input (text box)
  • sg.Input - There are two instances of this Element, which is a text box type Element. One to hold the MP4 file location and one to hold where you want to save the GIF
  • sg.FileBrowse - A button that opens a File browse dialog
  • sg.SaveAs - A button that opens a File save as dialog
  • sg.Button - A button that can do whatever you want it to do

Next, you take your list of Elements and pass it to sg.Window, which represents the window that holds all the other Elements. Your Window also has an exit button, a minimize button, and a title bar.

To start your GUI's event loop, you create a while loop and read from the Window object. This allows you to extract the values of the two sg.Input() objects, which contain the paths to the MP4 and GIF files.

When the user presses button labeled "Convert to GIF", you catch that event and call convert_mp4_to_jpgs() followed by make_gif(). If all goes well, the video will be converted and you will see a popup dialog stating where the newly created GIF was saved.

Try running this code. You should see something like this:

MP4 to GIF Converter GUI

Pretty neat, eh?

Wrapping Up

Now you have all the pieces you need to convert MP4 video files into GIFs. There are several different things you can do to make your code better. For example, you can add more error handling to your code so that you don't overwrite your GIFs on accident.

You could also add some new UI elements to tell your code to resize the individual frames down to help make the GIFs smaller. You can read about that in How to Resize a Photo with Python. Another option would be to change the compression on each individual JPG, which would also reduce the size of the GIF.

There are lots of other fun ways to make this code better too. Think about it and you're sure to come up with a few new features yourself!

Copyright © 2021 Mouse Vs Python | Powered by Pythonlibrary