0

I am trying to read the frames of a video using this command but from a python script using the subprocess module. The output of the command is in string but since the frame count of a video is an integer I would like to have it as an int object in python. However, int() as shown here is not working as expected for I assumed I would get 321 as an int.

Terminal Output:

ValueError: invalid literal for int() with base 10: ''

I have tried solutions from here like float() but they are not working for me.

Code:

import subprocess

def main():
    shell_ffprobe_frame_count = ["ffprobe", "-v", "error", "-select_streams", "v:0", "-count_packets",    "-show_entries", "stream=nb_read_packets", "-of", "csv=p=0", "test.mkv"]
    output = subprocess.Popen(shell_ffprobe_frame_count, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)

    print(output.stdout.read()) # 321
    print(type(output.stdout.read())) # <class 'str'>

    print(int(output.stdout.read())) # should be '321'
    print(type(int(output.stdout.read()))) # should be 'int'

if __name__ == "__main__":
    main()

I got <class 'str'> as return type from the subprocess which I have never seen ever before. type('321') produces just str in my terminal. I do not understand the difference and I wager this is the reason for the error. Any help is appreciated.

Rashiq
  • 317
  • 1
  • 4
  • 13

2 Answers2

3

Your first call to output.stdout.read() consumes all the output from the process; all subsequent reads return the empty string (indicating end of file). Slurp the output once, store it in a variable, and perform your tests on that stored string:

import subprocess

def main():
    shell_ffprobe_frame_count = ["ffprobe", "-v", "error", "-select_streams", "v:0", "-count_packets",    "-show_entries", "stream=nb_read_packets", "-of", "csv=p=0", "test.mkv"]
    output = subprocess.Popen(shell_ffprobe_frame_count, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)

    outdata = output.stdout.read()
    print(outdata) # 321
    print(type(outdata)) # <class 'str'>

    print(int(outdata)) # should be 321
    print(type(int(outdata))) # should be 'int'

if __name__ == "__main__":
    main()

The <class 'str'> output is just how the str class itself is represented, it's what you should expect when printing the type of any str value.

Alternatively, since you seem to want to run the subprocess synchronously anyway, simplify things and use subprocess.run instead (a synchronous, all-in-one wrapper for Popen), so it captures/caches the output for you:

import subprocess

def main():
    shell_ffprobe_frame_count = ["ffprobe", "-v", "error", "-select_streams", "v:0", "-count_packets",    "-show_entries", "stream=nb_read_packets", "-of", "csv=p=0", "test.mkv"]
    output = subprocess.run(shell_ffprobe_frame_count, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)

    print(output.stdout) # 321
    print(type(output.stdout)) # <class 'str'>

    print(int(output.stdout)) # should be 321
    print(type(int(output.stdout))) # should be 'int'

if __name__ == "__main__":
    main()

which will also properly ensure the process completes and clean up allocated resources for it deterministically.

ShadowRanger
  • 143,180
  • 12
  • 188
  • 271
3

When you call read, two things happened: it returns '321', and it advances a pointer in the buffer providing the output. Calling read again doesn't re-read the last thing returned; it returns whatever is at the current position of that pointer.

Only your first call to read returned '321'. The second one also returned '', indicating the end of the stream, and the empty string is still a value of type str. The third call also returns '', which is not a valid argument to int.

You needed to save the return value if you want to use it again:

x = output.stdout.read()
print(type(x))  # <class 'str'>
print(int(x))  # 321
print(type(int(x)))  # <class 'int'>
chepner
  • 497,756
  • 71
  • 530
  • 681