How to choose where a file is saved using FFMPEG - python

I was wondering how I could go about doing telling FFMPEG where to save files it converts, it keeps making the files save way too deep in my computer.
check_output("ffmpeg -i " + location + " -vn -ar 44100 -ac 1 -b:a 32k -f wav audio.wav", shell=True)
I use that in my program to convert the files to wav.

The question is totally unrelated to python.
I suggest stopping using shell=True, because that invokes a shell to call your subprocess, adding useless overhead (invoke a process to invoke another process). Another advantage is that it allows you to provide the command line as a list of parameters, which frees you from quote/space hell and is simpler.
That said, just add the folder to the destination filename and it will be generated there:
filename = 'audio.wav'
destination = r'C:\Some\Folder'
subprocess.check_output(["ffmpeg",
"-i", location,
"-vn",
"-ar", "44100",
"-ac", "1",
"-b:a", "32k",
"-f", "wav",
os.path.join(destination, filename)
])

Related

How can I create a way to bulk cut audio?

I want to create an automated way to cut .mp3 files to 45 seconds.
So far I have been able to use ffmpeg to cut the audio to 45 seconds with this command:
ffmpeg -t 45 -i input.mp3 -acodec copy output.mp3
However this does not actually speed anything up, as if I have to do this with each file I might as well use audacity. I know that I should be able to use a .bat file to create a loop for this, however I don't know how to set up the loop. In python I would create a list of the file names in my directory with listdir:
fileNames = listdir(path),
and then create a for loop:
(something like
i = 1
for fileName in fileNames:
x = 2 * int(i)
ffmpeg -t 45 -i str(fileName)+'.mp3' -acodec copy str(x)+'.mp3'
that)
However I don't know how to create something like this in a .bat file. Some help with this, or a way to achieve this in python, would be much appreciated.
You can try using below script. Save the code into a *.bat file in the folder where you have your mp3 songs and execute it and it will process all your songs.
#ECHO OFF
setlocal enableextensions enabledelayedexpansion
set /a count = 1
for %%f in (*.mp3) do (
set "output=!count!.mp3"
ffmpeg -t 45 -i %%f -acodec copy !output!
set /a count+=1
)
endlocal

python subprocess with ffmpeg give no output

I m want to extract the scene change timestamp using the scene change detection from ffmpeg. I have to run it on a few hundreds of videos , so i wanted to use a python subprocess to loop over all the content of a folder.
My problem is that the command that i was using for getting these values on a single video involve piping the output to a file which seems to not be an option from inside a subprocess call.
this is my code :
p=subprocess.check_output(["ffmpeg", "-i", sourcedir+"/"+name+".mpg","-filter:v", "select='gt(scene,0.4)',showinfo\"","-f","null","-","2>","output"])
this one tell ffmpeg need an output
output = "./result/"+name
p=subprocess.check_output(["ffmpeg", "-i", sourcedir+"/"+name+".mpg","-filter:v", "select='gt(scene,0.4)',metadata=print:file=output","-an","-f","null","-"])
this one give me no error but doesn't create the file
this is the original command that i use directly with ffmpeg:
ffmpeg -i input.flv -filter:v "select='gt(scene,0.4)',showinfo" -f null - 2> ffout
I just need the ouput of this command to be written to a file, anyone see how i could make it work?
is there a better way then subprocess ? or just another way ? will it be easier in C?
You can redirect the stderr output directly from Python without any need for shell=True which can lead to shell injection.
It's as simple as:
with open(output_path, 'w') as f:
subprocess.check_call(cmd, stderr=f)
Things are easier in your case if you use the shell argument of the subprocess command and it should behave the same. When using the shell command, you can pass in a string as the command rather then a list of args.
cmd = "ffmpeg -i {0} -filter:v \"select='gt(scene,0.4)',showinfo\" -f {1} - 2> ffout".format(inputName, outputFile)
p=subprocess.check_output(cmd, shell=True)
If you want to pass arguments, you can easily format your string

Manipulating image output from shell in python without storing external file

I am trying to store an image that is the result of ffmpeg.
Using this command, I have frame.png as an external file output:
ffmpeg -flags2 +export_mvs -i video.avi -vf 'select=gte(n\,200),codecview=mv=pf+bf+bb' -vframes 1 frame.png
I want to be able to load the frame.png directly into python, maybe using openCV but without saving it in the computer.
I thought of something like this:
cmd = "ffmpeg -flags2 +export_mvs -i video.avi -vf 'select=gte(n\,200),codecview=mv=pf+bf+bb' -vframes 1 frame.png"
img = cv.imread(sp.Popen(cmd, shell=True, stdout = sp.PIPE, stderr = sp.PIPE).communicate()[0])
But I get an error:
TypeError: bad argument type for built-in operation
Any clue how to do this? The idea is, no frame.png should be generated as a file.
You can set the output file as /dev/stdout (you might need to specify the output format with -f)
Then you redirect your output to your python script like so
ffmpeg options /dev/stdout | python your_script.py
Then you can read this question to see how you can read an image from a file object. Just replace StringIO with sys.stdin

Why is Exiftool slow, memory hog reading from stdin; fast, small reading from disk

I'm calling exiftool to extract XMP tags like Description from large videos, 5GB and over. My application is Python and I have seen some files which exhaust memory; I invoke it like:
fp = open('9502_UAS_2.mov', 'rb')
CMD = 'exiftool -api largefilesupport=1 -sort -a -S -G -struct -j -'
exiftool = subprocess.Popen(CMD.split(),
stdin=fp,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
(json_bytes, stderr) = exiftool.communicate()
To isolate the problem, I've tried variants on the CLI. This shows that reading from a file on disk is fast and uses little RAM, while reading from STDIN (re-creating the file pointer read above) is very slow and uses a lot of RAM (I've removed the output JSON metadata below for clarity):
time exiftool -api largefilesupport=1 -sort -a -S -G -struct -j 9502_UAS.mov
real 0m0.196s
time cat 9502_UAS.mov | exiftool -api largefilesupport=1 -sort -a -S -G -struct -j -
real 0m33.514s
'top' showed the second one consumed up to 1.4GB RAM on this 5.1GB video file.
I'd like to understand why reading from STDIN is slow and consumes so much memory, so I can watch out for limits like memory exhaustion on my servers. Is exiftool reading sequentially through the entire STDIN stream buffering the file until it gets the binary info it needs to parse the metadata? Is it not seek()-ing back and forward to find what it needs?
Conversely, why is running it against a native disk file so quick? Is exiftool using a memory mapped filesystem to quickly jump to the sections of the file it needs to parse?
Ideally, I'd read from STDIN because the real application's file origin is an AWS S3 bucket and I don't want to copy the file to local AWS EC2 disk if I can avoid it, so any hints to make reading stdin efficient would help.
Thanks.
Well, you are passing the whole content to stdin in the example. This takes time, of course. It would be better to pass the file name to the external tool:
CMD = 'exiftool -api largefilesupport=1 -sort -a -S -G -struct -j {}'
exiftool = subprocess.Popen(CMD.format('9502_UAS_2.mov').split(),
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
json_bytes, stderr = exiftool.communicate()
When passing to stdin, first the whole file will be piped to the program, and only after this process is done, does the process stop (regardless of the tool having done its job already).
When the file is on a remote server, you either need to run this script on that server, copy the file to a local file, or read in the first n bytes of the file and pass only these to the exiftool. (Determining how large n has to be left as an exercise...)

Bash process substitution in Python with Popen

I'm attempting to create a looped video file by calling ffmpeg from the python subprocess library. Here's the part that's giving me problems:
import subprocess as sp
sp.Popen(['ffmpeg', '-f', 'concat', '-i', "<(for f in ~/Desktop/*.mp4; do echo \"file \'$f\'\"; done)", "-c", "copy", "~/Desktop/sample3.mp4"])
With the above code I'm getting the following error:
<(for f in /home/delta/Desktop/*.mp4; do echo "file '$f'"; done): No such file or directory
I did find a similarly phrased question here. But I'm not sure how the solution might apply to solving my issue.
Following the advice in the comments and looking elsewhere I ended up changing the code to this:
sp.Popen("ffmpeg -f concat -i <(for f in ~/Desktop/*.mp4; do echo \"file \'$f\'\"; done) -c copy ~/Desktop/sample3.mp4",
shell=True, executable="/bin/bash")
--which works fine. – moorej
If you need to parameterize input and output files, consider breaking out your parameters:
# sample variables
inputDirectory = os.path.expanduser('~/Desktop')
outputDirectory = os.path.expanduser('~/dest.mp4')
sp.Popen(['''ffmpef -f concat -i <(for f in "$1"/*; do
echo "file '$f'";
done) -c copy "$2" ''',
bash, # this becomes $0
inputDirectory, # this becomes $1
outputDirectory, # this becomes $2
], shell=True, executable="/bin/bash")
...as this ensures that your code won't do untoward things even when given an input directory with a hostile name like /uploads/$(rm -rf ~)'$(rm -rf ~)'. (ffmpeg is likely to fail to parse an input file with such a name, and if there's any video you don't want included in the current working directory but we'd need to know the escaping rules it uses to avoid that; but it's far better for ffmpeg to fail than to execute arbitrary code).

Categories