FFMPEG and Pythons subprocess

2019-01-23 18:26发布

问题:

I'm trying to write a gui for FFMPEG. I'm using pythons subprocess to create a ffmpeg process for every conversion I want. This works fine, but I'd also like a way to get the progress of the conversion, whether it failed or not etc. I figured I could do this by accessing the process's stdout like so:

Calling subprocess.Popen()

# Convert - Calls FFMPEG with current settings. (in a seperate
# thread.)
def convert(self):
    # Check if options are valid
    if self.input == "" or self.output == "":
        return False

# Make the command string
ffmpegString = self.makeString()

# Try to open with these settings
try:
    self.ffmpeg = subprocess.Popen(ffmpegString, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
except OSError:
    self.error.append("OSError: ")
except ValueError:
    self.error.append("ValueError: Couldn't call FFMPEG with these parameters")

# Convert process should be running now.

And reading stdout:

convert = Convert()
convert.input = "test.ogv"
convert.output = "test.mp4"
convert.output_size = (0, 0)

convert.convert()

while 1:
    print convert.ffmpeg.stdout.readline()

This works but, ffmpeg's status doesn't show. I'm assuming it has something to do with way ffmpeg refreshes it. Is there a way to access it?

回答1:

I've often noticed problems reading standard output (or even standard error!) with subprocess, due to buffering issues that are hard to defeat. My favorite solution, when I do need to read such stdout/stderr from the subprocess, is to switch to using, instead of subprocess, pexpect (or, on Windows, wexpect).



回答2:

Simply add ,universal_newlines=True to your subprocess.Popen line.

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)

For now you got line in cycle like:

frame= 1900 fps=453 q=18.6 Lsize=    3473kB time=00:01:16.08 bitrate= 373.9kbits/s

Use the time= value to determine progress in percentage.



回答3:

I think you can't use readline because ffmpeg never prints one line, the status is updated by writing \r (carrige return) and then writing the line again.

size=      68kB time=0.39 bitrate=1412.1kbits/s    \rsize=    2786kB time=16.17 bitrate=1411.2kbits/s    \rsize=    5472kB time=31.76 bitrate=1411.2kbits/s    \r\n

If you examine the row above you'll notice that there is only one \n and that gets printed when the file is done converting.



回答4:

Since ffmpeg writes the data unflushed to stderr you have to set the stderr file descriptor to non-blocking using fcntl.

    fcntl.fcntl(
        pipe.stderr.fileno(),
        fcntl.F_SETFL,
        fcntl.fcntl(pipe.stderr.fileno(), fcntl.F_GETFL) | os.O_NONBLOCK,
    )

and then loop using select to read the data

    while True:
        readx = select.select([pipe.stderr.fileno()], [], [])[0]
        if readx:
            chunk = pipe.stderr.read()

for full example go here.



回答5:

FFMPEG:

FFMPEG output all the status text (what you see when you run it manually on the command line) on the stderr interface. In order to capture output from ffmpeg, you need to be watching the stderr interface - or redirecting it like the example.

Check for output on stderr:

Here is another way to try and read from stderr, instead of redirecting it when calling Popen

The Popen class in Python has an file object called stderr, you would access it in the same way that you are accessing stdout. I'm thinking your loop would look something like this:

while 1:
    print convert.ffmpeg.stdout.readline()
    print convert.ffmpeg.stderr.readline()

Disclaimer: I haven't tested this in Python, but I made a comparable application using Java.



回答6:

ffmpegCommand='''
ffmpeg
-f lavfi
-i anullsrc=channel_layout=1c:sample_rate=11025
-rtsp_transport tcp
-rtsp_transport udp
-rtsp_transport http
-thread_queue_size 32000
-i rtsp://xxx.xxx.xxx.xxx:554/user=admin&password=xxx&channel=1&stream=1.sdp?real_stream
-reconnect 1
-reconnect_at_eof 1
-reconnect_streamed 1
-reconnect_delay_max 4294
-tune zerolatency
-c:v copy
-c:a aac
-bufsize 6000k
-f flv rtmp://a.rtmp.youtube.com/live2/xxx-xxx-xxx-xxx'''
cmd=ffmpegCommand.split()
# "universal newline support" This will cause to interpret \n, \r\n and \r     equally, each as a newline.

p = subprocess.Popen(cmd, stderr=subprocess.PIPE, universal_newlines=True)
while True:    
        print(p.stderr.readline().rstrip('\r\n'))