Source code for auromat.util.movie

# Copyright European Space Agency, 2013

"""
A little helper module to create movies out of images using ffmpeg.
"""

from __future__ import absolute_import, print_function

import os
import errno
import tempfile
import shutil
import subprocess

[docs]def createMovie(moviePath, imagePaths, frameRate=25, crf=18, maxBitrate=2000, width=None, height=None, tempFolder=None): """ :param moviePath: path with .mp4 or .webm ending, will be overridden if existing :param imagePaths: paths of images in correct order :param frameRate: the frame rate of the movie :param crf: -crf parameter of ffmpeg, lower is better :param maxBitrate: maximum bitrate in kbit/s :param width, height: width and height of movie in pixels; if not given, then image dimensions are used :param tempFolder: the folder to store temporary data in (symlinks), will be removed afterwards """ if not tempFolder: tempFolder = tempfile.mkdtemp() elif not os.path.exists(tempFolder): os.makedirs(tempFolder) ext = os.path.splitext(imagePaths[0])[1] framePaths = [os.path.join(tempFolder, str(frame) + ext) for frame in range(len(imagePaths))] movieExt = os.path.splitext(moviePath)[1] try: # create symlinks in the temporary folder to the input images for imagePath, framePath in zip(imagePaths, framePaths): # TODO doesn't work on Windows os.symlink(imagePath, framePath) # run ffmpeg args = ['ffmpeg', '-y', '-r', str(frameRate), '-i', os.path.join(tempFolder, '%d' + ext), '-r', str(frameRate)] if width or height: if width and height: s = '{}:{}'.format(width, height) elif width: # makes sure that height is divisible by 2 s = '{}:trunc(ow/a/2)*2'.format(width) elif height: # makes sure that width is divisible by 2 s = 'trunc(oh/a/2)*2:{}'.format(height) args += ['-vf', 'scale=' + s] if movieExt == '.mp4': args += ['-codec:v', 'libx264', '-preset', 'slow', '-profile:v', 'baseline', '-pix_fmt', 'yuv420p', '-movflags', 'faststart'] # faststart needs ffmpeg >= 1.0 elif movieExt == '.webm': args += ['-codec:v', 'libvpx', '-quality', 'good', '-cpu-used', '0', '-b:v', str(maxBitrate) + 'k', '-qmin', '10', '-qmax', '42'] else: raise NotImplementedError('Unsupported video format: ' + ext) args += ['-crf', str(crf), '-maxrate', str(maxBitrate) + 'k', '-bufsize', str(maxBitrate*2) + 'k'] args += [moviePath] print('running ' + ' '.join(args)) try: process = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) except OSError as e: if e.errno == errno.ENOENT: raise RuntimeError('ffmpeg could not be launched. Make sure it is in the PATH!') else: raise stdout, _ = process.communicate() if process.returncode != 0: raise RuntimeError('ffmpeg returned exit code ' + str(process.returncode) + '; cmd line: ' + ' '.join(args) + '; ' + 'stdout&stderr output follows: ' + stdout.decode('ascii')) finally: shutil.rmtree(tempFolder)