11

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?

Esteban Küber
  • 36,388
  • 15
  • 79
  • 97

6 Answers6

11

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.

  • Interesting solution, but add some buffering issues with Popen. Won't work straight out of the box if you're looking for live encoding monitoring. – littlebridge Apr 15 '16 at 17:10
  • That works! Thank you. But why adding `universal_newlines=True` would make it work? – Qin Heyang Jul 07 '22 at 04:11
8

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).

Alex Martelli
  • 854,459
  • 170
  • 1,222
  • 1,395
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.

Linus Unnebäck
  • 23,234
  • 15
  • 74
  • 89
3

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.

Mitchell Currie
  • 2,769
  • 3
  • 20
  • 26
  • I'm not sure if this is still true... I get progressive output just doing a readline on stderr in a current build of ffmpeg on py2.7: `for line in proc.stderr: print line proc.stderr.flush()` – Anentropic Jul 25 '12 at 16:07
  • ah no, well, sort of. I am not getting progressive output of the `frame= xxx` status output during encoding (since this is one line that gets updated repeatedly) but I see the metadata lines then it blocks until encoding is finished, only showing the last status update, then it shows the remaining summary lines. – Anentropic Jul 25 '12 at 16:19
-1

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.

Astra
  • 10,735
  • 3
  • 37
  • 41
-2
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'))
Petr J
  • 11
  • 1