ffmpeg-python

June 19, 2022

I wanted to generate a slideshow from some images, adding captions along the way and some fades. I decided to do this using ffmpeg-python, and to do it inside a docker container.

JPG to MP4

This function is used to convert a static JPG image file into a MP4 of specified duration, with a fade in and fade out of 1 second at either end.

duration is the number of seconds to show the image for. clear is the number of seconds to fade out the text before the image fades.

def jpgToMp4(inputFilename, outputFilename, lineOneText, lineTwoText, duration=7, clear=2, background='black'):
    SCALE_OPTIONS = {
        'force_original_aspect_ratio': 'decrease',
    }
    PAD_OPTIONS = {
        'width': FF_OUTPUT_WIDTH,
        'height':FF_OUTPUT_HEIGHT,
        'x':'(ow-iw)/2',
        'y':'(oh-ih)/2',
        'color': background,
    }
    FADE_IN_OPTIONS = {
        'type': 'in',
        'start_time': '0',
        'duration': '1',
    }
    FADE_OUT_OPTIONS = {
        'type': 'out',
        'start_time': '%s' % (duration-1),
        'duration': '1',
    }

    LINE_ONE_TEXT_OPTIONS = {
        'text': lineOneText,
        'x': '((w-text_w)/2)',
        'y': '(h-text_h-30)',
        'fontfile': 'LiberationSans-Regular.ttf',
        'fontsize': '40',
        'fontcolor': 'white',
        'borderw': '4',
        'bordercolor': 'black',
        'alpha': 'if(lt(t,0),0,if(lt(t,1),(t-0)/1,if(lt(t,%d),1,if(lt(t,%d),(1-(t-%d))/1,0))))' % (duration-clear-1, duration-clear, duration-clear-1), 
    }

    LINE_TWO_TEXT_OPTIONS = {
        'text': lineTwoText,
        'x': '((w-text_w)/2)',
        'y': '(h-text_h-80)',
        'fontfile': 'LiberationSans-Regular.ttf',
        'fontsize': '40',
        'fontcolor': 'white',
        'borderw': '4',
        'bordercolor': 'black',
        'alpha': 'if(lt(t,0),0,if(lt(t,1),(t-0)/1,if(lt(t,%d),1,if(lt(t,%d),(1-(t-%d))/1,0))))' % (duration-clear-1, duration-clear, duration-clear-1), 
    }

    stream = ffmpeg.input(inputFilename, t=str(duration), framerate=FF_FRAMERATE, loop=1)
    stream = stream.filter('scale', FF_OUTPUT_WIDTH, FF_OUTPUT_HEIGHT, **SCALE_OPTIONS)
    stream = stream.filter('pad', FF_OUTPUT_WIDTH, FF_OUTPUT_HEIGHT, **PAD_OPTIONS)
    stream = stream.filter('fade', **FADE_IN_OPTIONS)
    stream = stream.filter('fade', **FADE_OUT_OPTIONS)
    stream = stream.filter('drawtext', **LINE_ONE_TEXT_OPTIONS)
    stream = stream.filter('drawtext', **LINE_TWO_TEXT_OPTIONS)

    stream.output(outputFilename, pix_fmt='yuv420p').run()

Combine Into One Slideshow

With each JPG I want converted into an MP4, I now want to combined them all into one long video. This can be done with the following code which builds up a text file of entries pointing to the mp4 files, and then uses this to combine them all.

filelist = ""
for image in imageList:
        filelist += "file '%s'\n" % outputMp4Filename

with open('filelist.txt', "w") as filesToProcess:
    filesToProcess.write(filelist)

ff_output_filename = "output.mp4"
(
    ffmpeg
    .input('filelist.txt', format='concat', safe=0)
    .output(ff_output_filename, c='copy').run()
)