Search code examples
pythonffmpegsubprocessconcatenation

How to correctly pass subprocess arguments


I'm trying to automate concatenating a folder of (correctly formatted) mp4 video files.

(This edited version of my question reduces the problem to its lowest level of my confusion. The original title asked about the differences between subprocess.call and subprocess.run but it turns out the problem was elsewhere.)

Why does

subprocess.call('ffmpeg -hide_banner -loglevel error -i movie1.mp4 -i movie2.mp4 -i credits.mp4 \
-filter_complex "[0:v:0] [0:a:0] [1:v:0] [1:a:0][2:v:0][2:a:0]concat=n=3:v=1:a=1[outv][outa]"  \
-map "[outv]" -map "[outa]" "output.mp4"',shell=True)

work fine (where s is a string of inputs, and count is the number of inputs), but

#python3 
#makeFinalExample.py

import subprocess

s = '-i movie1.mp4 -i movie2.mp4 -i credits.mp4'
count = 3
print(f's prints out as: {s}')


commandList = ['ffmpeg',
                '-hide_banner',
                '-loglevel',
                'error',
                #str(s),
                '{0}'.format(s),
                '-filter_complex',
                "[0:v:0][0:a:0][1:v:0][1:a:0]concat=n={0}:v=1:a=1[outv][outa]".format(count),
                '-map',
                "[outv]",
                '-map',
                "[outa]",
                "output.mp4"]
print(f'the command list prints out as {commandList}')
subprocess.run(commandList)

gets the error (whether the string is delivered as str(s), or in the formatting shown...

Unrecognized option 'i movie1.mp4 -i movie2.mp4 -i credits.mp4'.
Error splitting the argument list: Option not found

Here is a printout of the input string

-i movie1.mp4 -i movie2.mp4 -i credits.mp4

And here is a printout of the commandList

['ffmpeg', '-hide_banner', '-loglevel', 'error', '-i movie1.mp4 -i movie2.mp4 -i credits.mp4', '-filter_complex', '[0:v:0][0:a:0][1:v:0][1:a:0]concat=n=3:v=1:a=1[outv][outa]', '-map', '[outv]', '-map', '[outa]', 'output.mp4']

Solution

  • This has nothing with subprocess.call vs subprocess.run to do, the difference is that you are using shell=True in the first case, and in the second case not. The two functions behave exactly the same in this, and almost every other regard (subprocess.run is a newer function which supports many, many more options and returns a more useful object, but in its most basic form, it performs exactly the same job, using the same API).

    The problem is that you need to split the string s into tokens just like you are splitting all the other command-line arguments (and the error message actually reveals this, but I suppose you have to know what to look for in order to catch it). When you omit shell=True each option needs to be a separate list item, like

    [ ..., '-i', 'movie1.mp4', '-i', 'movie2.mp4', '-i', 'credits.mp4', ...]
    

    The function shlex.split() can help you correctly split a command into tokens:

    commandList = [
        'ffmpeg',
        '-hide_banner',
        '-loglevel', 'error',
        *shlex.split(s),
        '-filter_complex',
        "[0:v:0][0:a:0][1:v:0][1:a:0]concat=n={0}:v=1:a=1[outv][outa]".format(count),
        '-map', "[outv]",
        '-map', "[outa]",
        "output.mp4"]
    

    but a much better design for your function is probably to allow the user to pass in just a list of input video file names, and take it from there. (Then I guess count will not need to be specified explicitly either; it's just the value of len(inputfilenames).)