如何使用带有字节而不是文件的python子进程

Posted

技术标签:

【中文标题】如何使用带有字节而不是文件的python子进程【英文标题】:How to use python subprocess with bytes instead of files 【发布时间】:2020-08-19 11:43:18 【问题描述】:

我可以使用 ffmpeg 将 mp4 转换为 wav,这样做:

ffmpeg -vn test.wav  -i test.mp4 

我也可以使用subprocess 来做同样的事情,只要我的输入和输出是文件路径。

但是,如果我想直接在字节上使用ffmpeg 或像io.BytesIO() 这样的“类文件”对象怎么办?

这是一个尝试:

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

给我:

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

当然,我可以使用临时文件来汇集我的字节,但我希望能够避免写入磁盘(因为这一步只是转换管道中的一个链接)。

【问题讨论】:

***.com/questions/31080829/… 值得一看 显然BytesIO 对象没有fileno() 方法——这并不奇怪,真的。 @DaveIdito:我尝试了一个单独的脚本,但得到了同样的错误。 @MisterMiyagi:你已经解决了我的元问题(但具体问题似乎更有机会在这里得到回答)。你说“实际文件,包括文件路径、stat、xattrs 等”。我的元是:“如何获得与 python 中的实际文件等效的内存中的文件?”。 BytesIO 并没有一路走好。那么如何才能走得更远呢? 您所描述的是又名“ram-disk”。 Python 没有,更不用说外部进程也可以使用的了。 【参考方案1】:

这是我最近提出的解决方案,尽管我使用 AWS 和 GCP 存储桶对象作为输入和输出。 无论如何,我都不是 python 专家,但这让我得到了我想要的结果。

您需要在本地机器上安装 ffmpeg 并将其添加到环境变量中才能访问 ffmpeg。

如果您使用云,则 ffmpeg 预装在谷歌云函数中,并且您可以利用 AWS 的存储库库中的 Lambda 层。

希望有人能从中受益。 :)

import subprocess

# tested against 'wav', 'mp3', 'flac', 'mp4'
desired_output = 'mp3'
track_input = 'C:\\Users\\.....\\track.wav'
track_output = f'C:\\Users\\......\\output_track.desired_output'

encoded_type = ''
format_for_conversion = desired_output 

if desired_output =='m4a':
    encoded_type= '-c:a aac'
    format_for_conversion = 'adts'

with open(track_input, "rb") as in_track_file:
    data = in_track_file.read()

input_track_data= bytearray(data)

# using pipe:0 refers to the stdin, pipe:1 refers to stdout
ffmpeg_command = f'ffmpeg  -i pipe:0 encoded_type -f format_for_conversion pipe:1 '

ffmpeg_process = subprocess.Popen(ffmpeg_command, stdin=subprocess.PIPE, stdout=subprocess.PIPE)

output_stream = ffmpeg_process.communicate(input_track_data)
# comes back as a tuple
output_bytes = output_stream[0]

with open(track_output, 'ab') as f:
    delete_content(f)
    f.write(output_bytes)

【讨论】:

感谢@sobblesbobbles 提供此解决方案。您的解决方案是否适用于视频?我想下载一个 ts 文件,转换为 h265 mp4,然后推送到 AWS,而不写入磁盘。当我用你的方法尝试这个时,似乎只有音轨通过。我的命令是ffmpeg -i pipe:0 -c:v libx265 -c:a copy -f avi pipe:1。旁注,您的 delete_content() 函数尚未在您的示例中定义(无论如何似乎都不需要)【参考方案2】:

基于@thorwhalen 的回答,这是从字节到字节的工作方式。 您可能缺少@thorwhalen,是与进程交互时发送和获取数据的实际管道到管道方式。发送字节时,应该在进程读取它之前关闭标准输入。

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

【讨论】:

【参考方案3】:

这里是部分答案:三个函数展示了如何从文件到文件(为了完整性)、从字节到文件以及从文件到字节。字节到字节的解决方案正在反击。

import shlex
import subprocess

def from_file_to_file(input_file: str, output_file: str, action="-f wav -acodec pcm_s16le -ac 1 -ar 44100"):
    command = f"ffmpeg -i input_file action -vn output_file"
    subprocess.call(shlex.split(command))


def from_file_to_bytes(input_file: str, action="-f wav -acodec pcm_s16le -ac 1 -ar 44100"):
    command = f"ffmpeg -i input_file action -"

    ffmpeg_cmd = subprocess.Popen(
        shlex.split(command),
        stdout=subprocess.PIPE,
        shell=False
    )
    b = b''
    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


def from_bytes_to_file(input_bytes, output_file, action="-f wav -acodec pcm_s16le -ac 1"):
    command = f"ffmpeg -i /dev/stdin action -vn output_file"
    ffmpeg_cmd = subprocess.Popen(
        shlex.split(command),
        stdin=subprocess.PIPE,
        stdout=subprocess.PIPE,
        shell=False
    )
    ffmpeg_cmd.communicate(input_bytes)

【讨论】:

以上是关于如何使用带有字节而不是文件的python子进程的主要内容,如果未能解决你的问题,请参考以下文章

使用子进程时如何在 Python 中复制 tee 行为?

Python线程化多个bash子进程?

如何读取子进程标准输出的第一个字节,然后在 Python 中丢弃其余字节?

如何读取子进程标准输出的第一个字节,然后在 Python 中丢弃其余字节?

如何在 Django 网站中使用子进程模块

您如何将 zip 文件中的文件作为文本而不是字节读取?