Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use python subprocess with bytes instead of files

I can convert a mp4 to wav, using ffmpeg, by doing this:

ffmpeg -vn test.wav  -i test.mp4 

I can also use subprocess to do the same, as long as my input and output are filepaths.

But what if I wanted to use ffmpeg directly on bytes or a "file-like" object like io.BytesIO()?

Here's an attempt at it:

import subprocess
from io import BytesIO
b = BytesIO()

with open('test.mp4', 'rb') as stream:
    command = ['ffmpeg', '-i']
    proc = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=b)
    proc.communicate(input=stream.read())
    proc.wait()
    proc.stdin.close()
    proc.stdout.close()

Gives me:

---------------------------------------------------------------------------
UnsupportedOperation                      Traceback (most recent call last)
<ipython-input-84-0ddce839ebc9> in <module>
      5 with open('test.mp4', 'rb') as stream:
      6     command = ['ffmpeg', '-i']
----> 7     proc = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=b)
...
   1486                 # Assuming file-like object
-> 1487                 c2pwrite = stdout.fileno()
   1488 
   1489             if stderr is None:

UnsupportedOperation: fileno

Of course, I could use temp files to funnel my bytes, but I'd like to be able to avoid writing to the disk (because this step is just one link in a pipeline of transformations).

like image 712
thorwhalen Avatar asked May 04 '20 22:05

thorwhalen


1 Answers

Base on @thorwhalen's answer, here's how it would work from bytes to bytes. What you were probably missing @thorwhalen, is the actual pipe-to-pipe way to send and get data when interacting with a process. When sending bytes, the stdin should be closed before the process can read from it.

def from_bytes_to_bytes(
        input_bytes: bytes,
        action: str = "-f wav -acodec pcm_s16le -ac 1 -ar 44100")-> bytes or None:
    command = f"ffmpeg -y -i /dev/stdin -f nut {action} -"
    ffmpeg_cmd = subprocess.Popen(
        shlex.split(command),
        stdin=subprocess.PIPE,
        stdout=subprocess.PIPE,
        shell=False
    )
    b = b''
    # write bytes to processe's stdin and close the pipe to pass
    # data to piped process
    ffmpeg_cmd.stdin.write(input_bytes)
    ffmpeg_cmd.stdin.close()
    while True:
        output = ffmpeg_cmd.stdout.read()
        if len(output) > 0:
            b += output
        else:
            error_msg = ffmpeg_cmd.poll()
            if error_msg is not None:
                break
    return b
like image 99
vvm Avatar answered Sep 28 '22 00:09

vvm