Images to video (sci. vis. util)

TL;DR; Summary & Code

Use the below Python snippet to create an mp4/webm video based on images in a path that all have a prefix (which can be an empty string too of course). It requires you to have setup ffmpeg to be found on the command line (there would be ways to use the Python version too but this way you can also simply create a link to the standalone version if you are on *nix). The gist version on my github should always be up to date.

Python
# -*- coding: utf-8 -*-
"""
At some point in time

@author: elbarto
"""

def img2vid(path,
            prefix,
            moviename='auto',
            movietype='mp4',
            outrate=15,
            inrate=15,
            imgtype='auto',
            width=1280,
            height=960,
            preset='fast',
            quiet=True,
            )
    """
    Create a movie (mp4 or webm) from a series of images from a folder.

    Parameters
    ----------
    path : str
        Where the images are located.
    prefix : str
        The prefix the images have, e.g., img_XX.png
    moviename : str
        The name of the movie that should be written out e.g., img_XX.png
        The default is movie_XX.mp4 where XX is checked avoid overwriting.
    movietype : str
        The format of the movie to be created e.g., mp4 or webm
        The default is mp4, see also parameter moviename.
    outrate : int, optional
        The framerate of the input. The default is 15.
    inrate : int, optional
        The framerate of the output video. The default is 15.
    imgtype : str, optional
        The imagetype to use as input. The default is 'auto',
        which means that jpg, jpeg, png, gif are looked at and collected
    width : int, optional
        The width of the output video. The default is 1280.
    height : int, optional
        The height of the output video. The default is 960.
    preset : str, optional
        The preset for video creation, determining the creation speed.
        The default is 'fast', other options are very_fast, medium, slow...
    quiet : bool, optional
        Whether to print progress to stdout or not. The default is True.

    Returns
    -------
    None.

    """
    import os
    import subprocess

    # cheap implementation of natsort to avoid dependency
    def natsorted(listlike):
        import re
        convert = lambda x: int(x) if x.isdigit() else x.lower()
        alphanum_key = lambda key: [convert(c) 
                                    for c in re.split('([0-9]+)', key)]
        return sorted(listlike, key=alphanum_key)

    # for convenience move to the path where the images are located
    # will change at the end to the original path again
    curdir = os.path.abspath(os.curdir)
    os.chdir(path)

    filelist = []

    for entry in os.scandir(path):
        if (not entry.name.startswith('.')
           and entry.is_file()
           and entry.name.startswith(prefix)):
            pass
        else:
            continue

        if imgtype == 'auto':
            imgtypes = ['png', 'jpeg', 'jpg', 'gif']
            chk = [entry.name.lower().endswith(_) for _ in imgtypes]
            if max(chk):
                filelist.append(entry.name)
                _imgtype = [_
                            for _ in imgtypes
                            if entry.name.lower().endswith(_)]
        else:
            if entry.name.lower().endswith(imgtype):
                filelist.append(entry.name)

    filelist = natsorted(filelist)

    if imgtype == 'auto':
        if len(_imgtype) != 1:
            print('Issues with autodetection of image format.',
                  'We found the formats', _imgtype,
                  'Please pass in type directly via imgtype=...')
            return False
        imgtype = _imgtype[0]

    if filelist == []:
        print('No files found with these parameters')

    else:

        if not imgtype.startswith('.'):
            imgtype = '.' + imgtype

        cmd = "ffmpeg -r "
        cmd += f'{inrate} '
        cmd += " -f concat "

        tmpfile = 'temp_filelist.txt'
        with open(path + tmpfile, 'w') as fo:
            for file in filelist:
                fo.writelines('file ' + (file).replace('/', "\\") + '\n')

        cmd += f' -i {tmpfile}'
        cmd += ' -vcodec libx264'
        cmd += f' -preset {preset} '
        cmd += '-pix_fmt yuv420p -r '
        cmd += str(outrate)
        cmd += ' -y -s ' + f'{width}x{height} '

        # may be an issue if you have 1382195208752376502350 movie files in
        # the same folder which we hope is unlikely!
        startnumber = 0
        while os.path.exists(path+f'movie_{startnumber}.mp4'):
            startnumber += 1

        if moviename == 'auto':
            moviename = (f'movie_{startnumber}.{movietype}').replace('/', os.sep)

        cmd += moviename

        try:
            if not quiet:
                print('Calling', cmd)
            subprocess.check_call(cmd.split())
            print(f'Successfully made movie {path+os.sep + moviename}')
        except subprocess.CalledProcessError:
            print('Calling ffmpeg failed!',
                  'Make sure it is installed on your system via conda/pip/...')
        finally:
            pass
            os.chdir(curdir)
            os.remove(tmpfile)

        return path + os.sep + moviename

Background & motivation

Who doesn’t know it? You have to give a talk, illustrate your findings or simply want to show something extra on your poster at a conference with a tablet or linked via QR code. Now you can upload your image sequence to many online pages that will convert it into a format of your choice. After you made the video, you notice a mistake in the images and you have to redo it – maybe more than once even. If you want several videos, repeat the process even more often.

Instead, you could use video software suites that render the images into videos, but this is essentially the same tedious process and often requires you to learn the software (which has its own merit but maybe you are lacking the time). Why not program it instead?

The requirements are actually quite easy to meet, especially if we are using ffmpeg and Python. This requires you to have setup ffmpeg so it can be found on the command line.

Development process

To simplify the process, let’s look at the requirements for the function that were important for me at the time:

  • Call ffmpeg on the command line
  • Name the movie and do not overwrite existing movies
  • Pass in a path of images or a list of files (handy if you store the output from another script)
  • Which kind of move to make (mp4 usually is compatible the most, but webm is also useful when making videos for the web/browsers – I tend to go with mp4 for powerpoint presentations, but webm is also supported by MS Office 365 nowadays)
  • How fast the movie should play (in/outrate)
  • Which image type the images are (read jpeg, jpg, png gif are fine)
  • The dimensions the video should have (width, height), per default the first image dimension is taken
  • How the rendering by ffmpeg should be done (fast is usually good enough quality, there is a tradeoff, see documentation of ffmpeg)
  • Whether to report some progress during the making – aka the quiet option

Some things to consider are:

  • You could use natsorted, to get a natural sort of the files as that is usually how we humans would sort them. Usually, this makes little difference but natural sorting works better with mixed naming conventions (0001, 0010, 0100 … vs 1, 10, 100 …). Instead of another dependency, a cheap natsort is implemented as well. Replace the function if you actually have natsort installed and want to use it instead.
  • Instead you can also directly run the video command via ffmpeg on the command line – this is just a thin wrapper to keep some default options in place that made sense to me. You could also write files out to a file and load them via ffmpeg instead …

Other than that, the process is straightforward. Pass in your directory and the prefix that the images might have (tune some things if you want to). Otherwise, enjoy your video making and as a teaser, the following timelapse is made via the above script on a regular basis and linked here

Further reading and resources