Skip to content

Instantly share code, notes, and snippets.

@spirrobe
Created July 3, 2023 17:39
Show Gist options
  • Select an option

  • Save spirrobe/48f8c8fa033957f0f9798f085835bf22 to your computer and use it in GitHub Desktop.

Select an option

Save spirrobe/48f8c8fa033957f0f9798f085835bf22 to your computer and use it in GitHub Desktop.
A python wrapper for a subprocess call of ffmpeg (needs to be callable on command line) to make a video (controllable out/in rate) of a directory of images (any supported type, i.e. png, jpg, gif (untested)). Handy to create animation for teaching, websites and presentations
# -*- coding: utf-8 -*-
"""
Created on Tue May 24 17:10:42 2022
@author: rspirig
"""
def img2vid(path_or_list_of_files,
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)
if isinstance(path_or_list_of_files, str):
path = path_or_list_of_files
else:
path = os.path.dirname(path_or_list_of_files[0]) + os.sep
if path.endswith(os.sep):
pass
else:
path += os.sep
os.chdir(path)
if isinstance(path_or_list_of_files, str):
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)
else:
filelist = []
for entry in path_or_list_of_files:
entry = entry.split(os.sep)[-1]
if imgtype == 'auto':
imgtypes = ['png', 'jpeg', 'jpg', 'gif']
chk = [entry.lower().endswith(_) for _ in imgtypes]
if max(chk):
filelist.append(entry)
_imgtype = [_
for _ in imgtypes
if entry.lower().endswith(_)]
else:
if entry.name.lower().endswith(imgtype):
filelist.append(entry)
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 + moviename}')
except subprocess.CalledProcessError:
print('Calling ffmpeg failed!',
'Make sure it is installed on your system via conda/pip/...')
finally:
pass
os.remove(tmpfile)
os.chdir(curdir)
return path + os.sep + moviename
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment