Using wildcards in subprocess.Popen - python

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.

Related

I cannot convert a set of images taken from a FTP to a video using FFMPEG

I am writting a python code where I am trying to convert a set of images that I take from a FTP into a video using FFMPEG but I cannot. I have tried, instead of reading the folder where the images are, to read a txt file with the name of the images that I want to use, with the format needed in order that FFMPEG could read it propertly, but I get always the same error: Protocol 'ftp' not on whitelist 'tcp'
In the same code, I also try to change the format of one video and change the resolution and size, and this part of code works well.
However, writting as input the same reference of the FTP, the images' code fail and the video's code works.
Besides, I have tried in my terminal as local the same command I write in the code for the images, and in local it works propertly, but not in the code.
Here there is a part of my code:
Video's code (it works):
command = """ffmpeg -i {i} -an -crf {r} {o}""".format(i=src_path,o=path,r=resolution)
An example of this command when I run this is the next (I dont want to write the exact ip and port):
ffmpeg -i ftp://user:user#ip:port/landing_ffmpeg/pruebas/pruebahd.mp4 -an -crf 45 tmp/pruebasalida456.mp4
And next the images' code (it doesnt work):
command = """ffmpeg -loop 1 -framerate {ips} -i {i} -t 10 -pix_fmt yuv420p {o}""".format(i=src_path,o=path,ips=img_per_sec)
An example of this command is the next:
ffmpeg -loop 1 -framerate 2 -i ftp://user:user#ip:port/landing_ffmpeg/pruebas/prueba_imagenes/prueba06.jpg -t 10 -pix_fmt yuv420p tmp/videoimagen.mp4
And the error I get with this code is the next:
[ftp # 0x560eb3e11800] Protocol 'ftp' not on whitelist 'tcp'!
[image2 # 0x560eb3e09380] Could not open file : ftp://user:user#ip:port/landing_ffmpeg/pruebas/prueba_imagenes/prueba06.jpg
I dont get this error when I try to run the command of the video, only for the images. And both commands run propertly when I write in my terminal in local, with local paths.
I would appreciate if someone can help me to solve the problem and fix my code.
Thanks!
The error is saying it all. Try to whitelist the ftp:
ffmpeg -protocol_whitelist ftp -loop 1 -framerate 2 \
-i ftp://user:user#ip:port/landing_ffmpeg/pruebas/prueba_imagenes/prueba06.jpg \
-t 10 -pix_fmt yuv420p tmp/videoimagen.mp4

run bash script for each files in folder

i found this script but it need input filename and output filename to work
i'm windows user so i dont know how to run this script for each files in folder
what i want is :
sourcefile=$1 -> this should be input directory
destfile=$2 -> output directory or just originalfilename_preview
so when i'm try to excute script, it will run through files in input directory and excute two ffmpeg script inside
the first ffmpeg script will split video into multiple files in temp folder
the second ffmpeg merge those files in temp folder and complete the whole process with output folder or originalfilename_preview
-> loop for next files until completed
sourcefile=$1
destfile=$2
# Overly simple validation
if [ ! -e "$sourcefile" ]; then
echo 'Please provide an existing input file.'
exit
fi
if [ "$destfile" == "" ]; then
echo 'Please provide an output preview file name.'
exit
fi
# Get video length in seconds
length=$(ffprobe $sourcefile -show_format 2>&1 | sed -n 's/duration=//p' | awk '{print int($0)}')
# Start 20 seconds into the video to avoid opening credits (arbitrary)
starttimeseconds=20
# Mini-snippets will be 2 seconds in length
snippetlengthinseconds=2
# We'll aim for 5 snippets spread throughout the video
desiredsnippets=5
# Ensure the video is long enough to even bother previewing
minlength=$(($snippetlengthinseconds*$desiredsnippets))
# Video dimensions (these could probably be command line arguments)
dimensions=640:-1
# Temporary directory and text file where we'll store snippets
# These will be cleaned up and removed when the preview image is generated
tempdir=snippets
listfile=list.txt
# Display and check video length
echo 'Video length: ' $length
if [ "$length" -lt "$minlength" ]
then
echo 'Video is too short. Exiting.'
exit
fi
# Loop and generate video snippets
mkdir $tempdir
interval=$(($length/$desiredsnippets-$starttimeseconds))
for i in $(seq 1 $desiredsnippets)
do
# Format the second marks into hh:mm:ss format
start=$(($(($i*$interval))+$starttimeseconds))
formattedstart=$(printf "%02d:%02d:%02d\n" $(($start/3600)) $(($start%3600/60)) $(($start%60)))
echo 'Generating preview part ' $i $formattedstart
# Generate the snippet at calculated time
ffmpeg -i $sourcefile -vf scale=$dimensions -preset fast -qmin 1 -qmax 1 -ss $formattedstart -t $snippetlengthinseconds -threads $(nproc) $tempdir/$i.mp4
done
# Concat videos
echo 'Generating final preview file'
# Generate a text file with one snippet video location per line
# (https://trac.ffmpeg.org/wiki/Concatenate)
for f in $tempdir/*; do echo "file '$f'" >> $listfile; done
# Concatenate the files based on the generated list
ffmpeg -f concat -safe 0 -i $listfile -threads $(nproc) -an -tune zerolatency -x264opts bitrate=2000:vbv-maxrate=2000:vbv-bufsize=166 -vcodec libx264 -f mpegts -muxrate 2000K -y $destfile.mp4
echo 'Done! Check ' $destfile '.mp4!'
# Cleanup
rm -rf $tempdir $listfile
source: https://davidwalsh.name/video-preview
#Christopher Hoffman
wsl already installed, of course, i'm already run this script without problem but i need to manual input/output filename
./preview.sh input.mp4 out
#Renaud Pacalet
yes, all files in input directory or drag&drop files (but all files
in directory seem like easier)
i think modify script
output file have suffix in name "_preview"
if it have suffix, same input folder is ok,
video files (mkv,mp4,avi,..)
some files name have unicode character so i think input file will
inside " "
The easiest is probably to keep the script as it is and to use a bash loop to process all files in the input directory. Let's assume:
the input directory is /my/video/files,
you want to store all outputs in directory /some/where,
the script you show is in /else/where/myscript.sh,
you want to process all files in the input directory.
Just open a terminal where bash is the interactive shell and type:
shopt -s nullglob
chmod +x /else/where/myscript.sh
mkdir -p /some/where
cd /my/video/files
for f in *; do
/else/where/myscript.sh "$f" "/some/where/$f"
done
shopt -u nullglob
Explanations:
shopt -s nullglob enables the nullglob option. Without this, if there are no files at all in the input directory, there would still be one iteration of the loop with f=*. shopt -u nullglob disables it when we are done.
chmod +x /else/where/myscript.sh makes your script executable, just in case it was not already.
mkdir -p /some/where creates the output directory, just in case it did not exist yet.
cd /my/video/files changes the current directory to the input directory in which you have your video files.
for f in *; do loops over all files in the current directory (this is what the * stands for). In each iteration variable f is assigned the current file name.
/else/where/myscript.sh "$f" "/some/where/$f" executes your script with two parameters: the name of the input file and the name of the output file, both quoted with double quotes to prevent word splitting.
Note: if all files are not video files you can be more specific:
for f in *.mkv *.mp4 *.avi; do
...
Of course, for easier reuse, you can also create a new shell script file with all this.

Get result of ffmpeg and pass it to a python script

Pretty sure this is doable but I am not exactly sure on how to achieve it.
I have a Raspberry Pi streaming constantly on my local network and I use this ffmpeg script to save the video inside the Pi.
ffmpeg -i http://0.0.0.0:8080/stream/video.mjpeg -vcodec copy -map 0 -f segment -segment_time 5 -segment_format mp4 capture-%05d.mp4
The script is rather straightforward, it loads and saves 5 seconds video continuously in a local directory.
Here's what I am trying to do ultimately
Upload all saved videos to a Cloud Storage, then delete the local copy
I tried to pipe the output of ffmpeg to a python script like this but it does not work the way I imagine it would.
ffmpeg -i http://0.0.0.0:8080/stream/video.mjpeg -vcodec copy -map 0 -f segment -segment_time 5 -segment_format mp4 capture-%05d.mp4 | py test.py -p capture-%05d.mp4
This is my script, just to get the name/path of video
import argparse
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument('-p', '--videoPath', type=str, help="Path to recorded video", required=True)
args = parser.parse_args()
print(args.videoPath)
I was looking into the man page of ffmpeg and came across the loglevel option.
-loglevel [repeat+]loglevel | -v [repeat+]loglevel
Set the logging level used by the library. Adding "repeat+" indicates that repeated log output should not be compressed to the first line and the "Last message repeated n
times" line will be omitted. "repeat" can also be used alone. If "repeat" is used alone, and with no prior loglevel set, the default loglevel will be used. If multiple
loglevel parameters are given, using 'repeat' will not change the loglevel. loglevel is a string or a number containing one of the following values:
verbose, 40
Same as "info", except more verbose.
So here is a simple one-liner workaround to get the things working in your case -
ffmpeg -i my_vid_feed -vcodec copy -map 0 -f segment -segment_time 5 -loglevel 40 -segment_format mp4 capture-%05d.mp4 2>&1 | grep --line-buffered -Eo "segment:.+ended" | awk -F "'" '{print $2; system("")}' | xargs -n1 python my_processor.py -p
I am just parsing the output of the ffmpeg, when a new file is written completely the verbose log emits a line like this -
[segment # 0x7fc253817000] segment:'capture-00002.mp4' count:2 ended
So I am just taking the filename from the line and passing it to that argparse python file. The -n1 option in the xargs tells it to only pass one arg at a time to the python file and then execute the python script with that file as an argument.
Here is the output of the command in action -
root$ ffmpeg -i my_vid_feed -vcodec copy -map 0 -f segment -segment_time 5 -loglevel 40 -segment_format mp4 capture-%05d.mp4 2>&1 | grep --line-buffered -Eo "segment:.+ended" | awk -F "'" '{print $2; system("")}' | xargs -n1 python my_processor.py -p
started
capture-00000.mp4
ended
started
capture-00001.mp4
ended
started
capture-00002.mp4
ended
started
capture-00003.mp4
ended
The python file used - my_processor.py
import argparse
import time
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument('-p', '--videoPath', type=str, help="Path to recorded video", required=True)
args = parser.parse_args()
print("started")
print(args.videoPath)
time.sleep(3)
print("ended")
The only possible drawback to this would be at a time there will be only one instance of python running and your jobs would run sequentially after the previous job is done. In case you want to run them in parallel, you can do that as well.

subprocess call ffmpeg (command line)

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.

Passing double quote shell commands in python to subprocess.Popen()?

I've been trying to pass a command that works only with literal double quotes in the commandline around the "concat:file1|file2" argument for ffmpeg.
I cant however make this work from python with subprocess.Popen(). Anyone have an idea how one passes quotes into subprocess.Popen?
Here is the code:
command = "ffmpeg -i "concat:1.ts|2.ts" -vcodec copy -acodec copy temp.mp4"
output,error = subprocess.Popen(command, universal_newlines=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE).communicate()
When I do this, ffmpeg won't take it any other way other than quotes around the concat segement. Is there a way to successfully pass this line to subprocess.Popen command?
I'd suggest using the list form of invocation rather than the quoted string version:
command = ["ffmpeg", "-i", "concat:1.ts|2.ts", "-vcodec", "copy",
"-acodec", "copy", "temp.mp4"]
output,error = subprocess.Popen(
command, universal_newlines=True,
stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
This more accurately represents the exact set of parameters that are going to be passed to the end process and eliminates the need to mess around with shell quoting.
That said, if you absolutely want to use the plain string version, just use different quotes (and shell=True):
command = 'ffmpeg -i "concat:1.ts|2.ts" -vcodec copy -acodec copy temp.mp4'
output,error = subprocess.Popen(
command, universal_newlines=True, shell=True,
stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
Either use single quotes 'around the "whole pattern"' to automatically escape the doubles or explicitly "escape the \"double quotes\"". Your problem has nothing to do with Popen as such.
Just for the record, I had a problem particularly with a list-based command passed to Popen that would not preserve proper double quotes around a glob pattern (i.e. what was suggested in the accepted answer) under Windows. Joining the list into a string with ' '.join(cmd) before passing it to Popen solved the problem.
This works with python 2.7.3 The command to pipe stderr to stdout has changed since older versions of python:
Put this in a file called test.py:
#!/usr/bin/python
import subprocess
command = 'php -r "echo gethostname();"'
p = subprocess.Popen(command, universal_newlines=True, shell=True,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
text = p.stdout.read()
retcode = p.wait()
print text
Invoke it:
python test.py
It prints my hostname, which is apollo:
apollo
Read up on the manual for subprocess: http://docs.python.org/2/library/subprocess.html
I have been working with a similar issue, with running a relatively complex
command over ssh. It also had multiple double quotes and single quotes. Because
I was piping the command through python, ssh, powershell etc.
If you can instead just convert the command into a shell script, and run the
shell script through subprocess.call/Popen/run, these issues will go away.
So depending on whether you are on windows or on linux or mac, put the
following in a shell script either (script.sh or script.bat)
ffmpeg -i "concat:1.ts|2.ts" -vcodec copy -acodec copy temp.mp4
Then you can run
import subprocess; subprocess.call(`./script.sh`; shell=True)
Without having to worry about single quotes, etc.
This line of code in your question isn't valid Python syntax:
command = "ffmpeg -i "concat:1.ts|2.ts" -vcodec copy -acodec copy temp.mp4"
If you had a Python file with just this line in it, you would get a syntax error. A string literal surrounded with double quotes can't have double quotes in them unless they are escaped with a backslash. So you could fix that line by replacing it with:
command = "ffmpeg -i \"concat:1.ts|2.ts\" -vcodec copy -acodec copy temp.mp4"
Another way to fix this line is to use single quotes for the string literal in Python, that way Python is not confused when the string itself contains a double quote:
command = 'ffmpeg -i "concat:1.ts|2.ts" -vcodec copy -acodec copy temp.mp4'
Once you have fixed the syntax error, you can then tackle the issue with using subprocess, as explained in this answer. I also wrote this answer to explain a helpful mental model for subprocess in general.
Also struggling with a string argument containing spaces and not wanting to use the shell=True.
The solution was to use double quotes for the inside strings.
args = ['salt', '-G', 'environment:DEV', 'grains.setvals', '{"man_version": "man-dev-2.3"}']
try:
p = subprocess.Popen(args, stdin=subprocess.PIPE
, stdout=subprocess.PIPE
, stderr=subprocess.PIPE
)
(stdin,stderr) = p.communicate()
except (subprocess.CalledProcessError, OSError ) as err:
exit(1)
if p.returncode != 0:
print("Failure in returncode of command:")
Anybody suffering from this pain. It also works with params enclosed with quotation marks.
params = ["ls", "-la"]
subprocess.check_output(" ".join(params), shell=True)

Categories

Resources