def save_gif(gif_fname, images, fps):
    '\n    To generate a gif from image files, first generate palette from images\n    and then generate the gif from the images and the palette.\n    ffmpeg -i input_%02d.jpg -vf palettegen -y palette.png\n    ffmpeg -i input_%02d.jpg -i palette.png -lavfi paletteuse -y output.gif\n\n    Alternatively, use a filter to map the input images to both the palette\n    and gif commands, while also passing the palette to the gif command.\n    ffmpeg -i input_%02d.jpg -filter_complex "[0:v]split[x][z];[z]palettegen[y];[x][y]paletteuse" -y output.gif\n\n    To directly pass in numpy images, use rawvideo format and `-i -` option.\n    '
    from subprocess import Popen, PIPE
    (head, tail) = os.path.split(gif_fname)
    if (head and (not os.path.exists(head))):
        os.makedirs(head)
    (h, w, c) = images[0].shape
    cmd = ['ffmpeg', '-y', '-f', 'rawvideo', '-vcodec', 'rawvideo', '-r', ('%.02f' % fps), '-s', ('%dx%d' % (w, h)), '-pix_fmt', {1: 'gray', 3: 'rgb24', 4: 'rgba'}[c], '-i', '-', '-filter_complex', '[0:v]split[x][z];[z]palettegen[y];[x][y]paletteuse', '-r', ('%.02f' % fps), ('%s' % gif_fname)]
    proc = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
    for image in images:
        proc.stdin.write(image.tostring())
    (out, err) = proc.communicate()
    if proc.returncode:
        err = '\n'.join([' '.join(cmd), err.decode('utf8')])
        raise IOError(err)
    del proc
To generate a gif from image files, first generate palette from images
and then generate the gif from the images and the palette.
ffmpeg -i input_%02d.jpg -vf palettegen -y palette.png
ffmpeg -i input_%02d.jpg -i palette.png -lavfi paletteuse -y output.gif

Alternatively, use a filter to map the input images to both the palette
and gif commands, while also passing the palette to the gif command.
ffmpeg -i input_%02d.jpg -filter_complex "[0:v]split[x][z];[z]palettegen[y];[x][y]paletteuse" -y output.gif

To directly pass in numpy images, use rawvideo format and `-i -` option.
def save_gif(gif_fname, images, fps):
    '\n    To generate a gif from image files, first generate palette from images\n    and then generate the gif from the images and the palette.\n    ffmpeg -i input_%02d.jpg -vf palettegen -y palette.png\n    ffmpeg -i input_%02d.jpg -i palette.png -lavfi paletteuse -y output.gif\n\n    Alternatively, use a filter to map the input images to both the palette\n    and gif commands, while also passing the palette to the gif command.\n    ffmpeg -i input_%02d.jpg -filter_complex "[0:v]split[x][z];[z]palettegen[y];[x][y]paletteuse" -y output.gif\n\n    To directly pass in numpy images, use rawvideo format and `-i -` option.\n    '
    from subprocess import Popen, PIPE
    (head, tail) = os.path.split(gif_fname)
    if (head and (not os.path.exists(head))):
        os.makedirs(head)
    (h, w, c) = images[0].shape
    cmd = ['ffmpeg', '-y', '-f', 'rawvideo', '-vcodec', 'rawvideo', '-r', ('%.02f' % fps), '-s', ('%dx%d' % (w, h)), '-pix_fmt', {1: 'gray', 3: 'rgb24', 4: 'rgba'}[c], '-i', '-', '-filter_complex', '[0:v]split[x][z];[z]palettegen[y];[x][y]paletteuse', '-r', ('%.02f' % fps), ('%s' % gif_fname)]
    proc = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
    for image in images:
        proc.stdin.write(image.tostring())
    (out, err) = proc.communicate()
    if proc.returncode:
        err = '\n'.join([' '.join(cmd), err.decode('utf8')])
        raise IOError(err)
    del proc