subprocess call ffmpeg (command line) - python

I have been incorporating subprocess calls in my program. I have had no issues with subprocess calls for other commands, but I am having trouble getting the command line input
ffmpeg -r 10 -i frame%03d.png -r ntsc movie.mpg
To work inside a subprocess.call()
I tried the following with no success:
subprocess.call('ffmpeg -r 10 -i %s frame%03.d.png - r ntsc movie.mpg')
Any thoughts? Do I separate out different commands, do I specify string, integer etc. with %s, %d?

When you use subprocess, your command must either be a string that looks exactly like what you would type on the command line (and you set shell=True), or a list where each command is an item in the list (and you take the default shell=False). In either case, you have to deal with the variable part of the string. For instance, the operating system has no idea what "%03d" is, you have to fill it in.
I can't tell from your question exactly what the parameters are, but lets assume you want to convert frame 3, it would look something like this in a string:
my_frame = 3
subprocess.call(
'ffmpeg -r 10 -i frame%03d.png -r ntsc movie%03d.mpg' % (my_frame, my_frame),
shell=True)
Its kinda subtle in this example, but that's risky. Suppose these things were in a directory whose name name had spaces (e.g., ./My Movies/Scary Movie). The shell would be confused by those spaces.
So, you can put it into a list and avoid the problem
my_frame = 3
subprocess.call([
'ffmpeg',
'-r', '10',
'-i', 'frame%03d.png' % my_frame,
'-r', 'ntsc',
'movie%03d.mpg' % my_frame,
])
More typing, but safer.

I found this alternative, simple, answer to also work.
subprocess.call('ffmpeg -r 10 -i frame%03d.png -r ntsc '+str(out_movie), shell=True)

import shlex
import pipes
from subprocess import check_call
command = 'ffmpeg -r 10 -i frame%03d.png -r ntsc ' + pipes.quote(out_movie)
check_call(shlex.split(command))

'ffmpeg -r 10 -i frame%03d.png -r ntsc movie.mpg' should be fine. OTOH, If you don't need the power of frame%03d.png, frame*.png is a bit simpler.
If you want to "see the syntax for it if I replace 'movie.mpg' with a variable name", it looks something like this:
cmd = 'ffmpeg -r 10 -i "frame%%03d.png" -r ntsc "%s"' % moviename
We need to escape the % with an extra % to hide it from Python's % substitution machinery. I've also added double quotes " , to cope with the issues that tdelaney mentioned.

Related

How to run advance Linux command on python script

I want to get the string output of the following linux command
systemctl show node_exporter |grep LoadState| awk '{split($0,a,"="); print a[2]}'
I tried with
import subprocess
output = subprocess.check_output("systemctl show node_exporter |grep LoadState| awk '{split($0,a,"="); print a[2]}'", shell=True)
but the output is,
output = subprocess.check_output("systemctl show node_exporter |grep LoadState| awk '{split($0,a,"="); print a[2]}'", shell=True)
SyntaxError: keyword can't be an expression
Well,
First of all, the function takes a list of strings as a command, not a single string. E.g.:
"ls -a -l" - wrong
["ls", "-a", "-l"] - good
Secondly. If the linux command is super complex or contains lots of lines - it makes sense to create a separate bash file e.g. command.sh, put your linux commands there and run the script from python with:
import subprocess
output = subprocess.check_output(["./command.sh"], shell=True)
You need to escape the double quotes (because they indicate the begin/end of the string):
import subprocess
output = subprocess.check_output("systemctl show node_exporter |grep LoadState| awk '{split($0,a,\"=\"); print a[2]}'", shell=True)

Quotes without backslash in subprocess command line in python

I'm trying to use ffmpeg from python. The command I need to execute is:
ffmpeg -i test_file-1kB.mp4 -i test_file.mp4 -filter_complex psnr="stats_file=test_file.mp4-1kB.psnr" -f null -
However, my output that is getting passed to subprocess looks like it is escaping the double quotes with backslashes like so:
In[1]: print(subprocess.list2cmdline(psnr_args))
ffmpeg -i test_file-1kB.mp4 -i test_file.mp4 -filter_complex psnr=\"stats_file=test_file.mp4-1kB.psnr\" -f null -
To use subprocess, I build my command line arguments one at a time into a list and then pass the list to subprocess.
psnr_args = []
psnr_args.append("ffmpeg")
#add first input, the encoded video
psnr_args.append("-i")
psnr_args.append(full_output_file_name)
#add second input, the original video
psnr_args.append("-i")
psnr_args.append(video_file)
#Setup the psnr log file
psnr_args.append("-filter_complex")
psnr_args.append('psnr="stats_file=%s.psnr"' % vstats_abs_filename )
#Output the video to null
psnr_args.append("-f")
psnr_args.append("null")
psnr_args.append("-")
print(subprocess.list2cmdline(psnr_args))
run_info_psnr = subprocess.run(psnr_args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
After more fiddling, I found a solution that works in this case but may not work in all cases. If I use double quotes as the outer quotes and the single quotes as the inner quotes, the output to subprocess uses a single quote at the same location with no backslash. This is acceptable for ffmpeg. However, for others where double quotes are the only solution, it won't be a fix.
psnr_args.append("psnr='stats_file=%s.psnr'" % vstats_abs_filename )
Output to subprocess looks like this:
In[1]: print(subprocess.list2cmdline(psnr_args))
ffmpeg -i test_file-1kB.mp4 -i test_file.mp4 -filter_complex psnr='stats_file=test_file.mp4-1kB.psnr' -f null -
In shell, the argument:
psnr="stats_file=test_file.mp4-1kB.psnr"
Is absolutely identical to:
psnr=stats_file=test_file.mp4-1kB.psnr
The quotes are removed during the shell's own processing. They are not part of the command passed to ffmpeg, which doesn't expect or understand them. Because you're directly telling the Python subprocess module to invoke a literal argument vector, there's no shell involved, so shell syntax shouldn't be present.
This has something to do with ffmpeg AV filter chain syntax too. You need to run the command like xxxx -filter_complex "psnr='stats.txt'" xxxx. To get this, you should ensure the double quote that encapsulate the filter chain reaches inside. subproces expects a flat list as the first argument, where the command is the first entry. So ['ffmpeg', '-i', "t1.mp4", "-filter_compelx", '"psnr=\'stats.txt\'"', .... and so on ].

Using wildcards in subprocess.Popen

I have found a couple answers that solve the problem of passing wildcards through Popen, but I can't seem to get those solutions to work for my particular case.
I have a project that will merge an audio and video file when a button is pressed, and I need to use subprocess.Popen to execute the command I want:
mergeFile = "ffmpeg -i /home/pi/Video/* -i /home/pi/Audio/test.wav -acodec copy -vcodec copymap 0:v -map 1:a /home/pi/Test/output.mkv"
proc= subprocess.Popen(shlex.split(mergeFiles), shell=True)
I basically want to take whatever file is in my Video folder (my project downloads videos from a camera and the name of the file changes every time a new video is saved) and merge the audio from a separate "Audio" folder and then save that file to yet another folder.
I have tried setting the shell to true, and nothing works.
I also don't know how the glob module would help me in this scenario.
The problem here is the shlex.split(). When used in conjunction with shell=True, it means that only the string ffmpeg is treated as a script, and that the other components of your command line are passed as arguments to that script (which it never looks at / reads).
mergeFile = "ffmpeg -i /home/pi/Video/* -i /home/pi/Audio/test.wav -acodec copy -vcodec copymap 0:v -map 1:a /home/pi/Test/output.mkv"
proc = subprocess.Popen(mergeFile, shell=True)
A better-practice alternative that still uses shell=True (if you're actually parameterizing the directory names and filenames) might be:
mergeFile=[
'ffmpeg -i "$1"/* -i "$2" -acodec copy -vcodec copymap 0:v -map 1:a "$3"',
'_', # placeholder for $0
"/home/pi/Video", # directory for $1 -- can use a variable here
"/home/pi/Audio/test.wav",
"/home/pi/Test/output.mkv",
]
subprocess.Popen(mergeFile, shell=True)
...in which case the script itself is constant (and can't have its meaning changed by values injected via filenames or other parameters), but out-of-band data can be provided.
Even better than that is to stop using shell=True altogether. Consider:
import subprocess, glob
mergeFile=[
'ffmpeg', '-i',
] + (glob.glob('/home/pi/Video/*') or ['/home/pi/Video/*']) + [
'-i', '/home/pi/Audio/test.wav',
'-acodec', 'copy',
'-vcodec', 'copymap', '0:v',
'-map', '1:a1',
'/home/pi/Test/output.mkv'
]
subprocess.Popen(mergefile)
The or ['/home/pi/Video/*'] exists to cause the same error message you'd get with a shell if no files matching the glob exist. Obviously, you could just abort in that case as well.

Subprocess.call ffmpeg making empty file

I am trying to convert video from one format to other.
extension = '.avi'
extension_less_url = '../uploads/video'
import subprocess
subprocess.call(['ffmpeg', '-i', extension_less_url + extension, extension_less_url + '.mp4'])
But above produces an empty file named 'video.mp4'.
How to correct?
This is the error I am getting:
The encoder 'aac' is experimental but experimental codecs are not enabled, add '-strict -2' if you want to use it.
This problem is for '.mp4'. try something else like '.mkv' for the target format of your video.
I've done this and the problem is solved
import subprocess
subprocess.call('ffmpeg -f vfwcap -t 10 -r 25 -i 0 c:/test/sample11.avi')
python have a subprocess module which helps for running exec command.
In case if you want to run multiple cmd or shell commands one after another you can use the following :
subprocess.check_call("cd /home/pi/images; ls", shell=True)
here : "cd /home/pi/images" is one command which is separated using ";" to run another command "ls"

Python os.system timeout with strings

I'm unable to execute the following line:
os.system("timeout 1s bash -c \"ffmpeg -i \""+path+\"+" | <some_<other_cmd>\"")
So the purpose of this command is to set a timeout for the whole command, i.e. pipelining some ffmpeg information from a path.
The problem is because bash -c "CMD" is expected, but the command also contains " ".
Is there another way of defining the \"path\", because the path can contain spaces? Or another solution which can resolve my problem?
Thanks in advance!
Triple sinqle quotes can do the trick (so that you do not have to escape doublequotes):
os.system('''timeout 1s bash -c "ffmpeg -i "+path+"+" | cat''')
But in general.. Why not use subprocess.call that has saner syntax?
Has answered similar question in other posts: 1 and 2
you can use subprocess related functions, which all support timeout parameter, to replace os.system
such as subprocess.check_output
ffmpegCmd = "ffmpeg -I %s | %s" % (path, someOtherCmd)
outputBytes = subprocess.check_output(ffmpegCmd, shell=True, timeout=1)
outputStr = outputBytes.decode("utf-8") # change utf-8 to your console encoding if necessary

Categories

Resources