Getting realtime output from ffmpeg to be used in progress bar (PyQt4, stdout)
In this specific case for capturing ffmpeg's status output (which goes to STDERR), this SO question solved it for me: FFMPEG and Pythons subprocess
The trick is to add universal_newlines=True
to the subprocess.Popen()
call, because ffmpeg's output is in fact unbuffered but comes with newline-characters.
cmd = "ffmpeg -i in.mp4 -y out.avi"
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,universal_newlines=True)
for line in process.stdout:
print(line)
Also note that in this code sample the STDERR status output is directly redirected to subprocess.STDOUT
- Calling from the shell is generally not required.
- I know from experince that part of the ffmpeg output comes on
stderr
, notstdout
.
If all you want to do is print the output line, like in your example above, then simply this will do:
import subprocess
cmd = 'ffmpeg -i file.mp4 file.avi'
args = cmd.split()
p = subprocess.Popen(args)
Note that the line of ffmpeg chat is terminated with \r
, so it will overwrite in the same line! I think this means you can't iterate over the lines in p.stderr
, as you do with your rsync example. To build your own progress bar, then, you may need to handle the reading yourself, this should get you started:
p = subprocess.Popen(args, stderr=subprocess.PIPE)
while True:
chatter = p.stderr.read(1024)
print("OUTPUT>>> " + chatter.rstrip())
The only way I've found to get dynamic feedback/output from a child process is to use something like pexpect:
#! /usr/bin/python
import pexpect
cmd = "foo.sh"
thread = pexpect.spawn(cmd)
print "started %s" % cmd
cpl = thread.compile_pattern_list([pexpect.EOF,
'waited (\d+)'])
while True:
i = thread.expect_list(cpl, timeout=None)
if i == 0: # EOF
print "the sub process exited"
break
elif i == 1:
waited_time = thread.match.group(1)
print "the sub process waited %d seconds" % int(waited_time)
thread.close()
the called sub process foo.sh just waits a random amount of time between 10 and 20 seconds, here's the code for it:
#! /bin/sh
n=5
while [ $n -gt 0 ]; do
ns=`date +%N`
p=`expr $ns % 10 + 10`
sleep $p
echo waited $p
n=`expr $n - 1`
done
You'll want to use some regular expression that matches the output you're getting from ffmpeg and does some kind of calculation on it to show the progress bar, but this will at least get you the unbuffered output from ffmpeg.
This answers didn't worked for me :/ Here is the way I did it.
Its from my project KoalaBeatzHunter.
Enjoy!
def convertMp4ToMp3(mp4f, mp3f, odir, kbps, callback=None, efsize=None):
"""
mp4f: mp4 file
mp3f: mp3 file
odir: output directory
kbps: quality in kbps, ex: 320000
callback: callback() to recieve progress
efsize: estimated file size, if there is will callback() with %
Important:
communicate() blocks until the child process returns, so the rest of the lines
in your loop will only get executed after the child process has finished running.
Reading from stderr will block too, unless you read character by character like here.
"""
cmdf = "ffmpeg -i "+ odir+mp4f +" -f mp3 -ab "+ str(kbps) +" -vn "+ odir+mp3f
lineAfterCarriage = ''
print deleteFile(odir + mp3f)
child = subprocess.Popen(cmdf, shell=True, stderr=subprocess.PIPE)
while True:
char = child.stderr.read(1)
if char == '' and child.poll() != None:
break
if char != '':
# simple print to console
# sys.stdout.write(char)
# sys.stdout.flush()
lineAfterCarriage += char
if char == '\r':
if callback:
size = int(extractFFmpegFileSize(lineAfterCarriage)[0])
# kb to bytes
size *= 1024
if efsize:
callback(size, efsize)
lineAfterCarriage = ''
Next, you need 3 more functions to implement it.
def executeShellCommand(cmd):
p = Popen(cmd , shell=True, stdout=PIPE, stderr=PIPE)
out, err = p.communicate()
return out.rstrip(), err.rstrip(), p.returncode
def getFFmpegFileDurationInSeconds(filename):
cmd = "ffmpeg -i "+ filename +" 2>&1 | grep 'Duration' | cut -d ' ' -f 4 | sed s/,//"
time = executeShellCommand(cmd)[0]
h = int(time[0:2])
m = int(time[3:5])
s = int(time[6:8])
ms = int(time[9:11])
ts = (h * 60 * 60) + (m * 60) + s + (ms/60)
return ts
def estimateFFmpegMp4toMp3NewFileSizeInBytes(duration, kbps):
"""
* Very close but not exact.
duration: current file duration in seconds
kbps: quality in kbps, ex: 320000
Ex:
estim.: 12,200,000
real: 12,215,118
"""
return ((kbps * duration) / 8)
And finally you do:
# get new mp3 estimated size
secs = utls.getFFmpegFileDurationInSeconds(filename)
efsize = utls.estimateFFmpegMp4toMp3NewFileSizeInBytes(secs, 320000)
print efsize
utls.convertMp4ToMp3("AwesomeKoalaBeat.mp4", "AwesomeKoalaBeat.mp3",
"../../tmp/", 320000, utls.callbackPrint, efsize)
Hope this will help!