英文:
How to use ffmpeg in flask server to convert between audio formats without writing files to disk?
问题
我已成功使用Python中的ffmpeg来转换一些音频文件的格式,如下所示:
command = "ffmpeg -i audio.wav -vn -acodec pcm_s16le output.wav"
subprocess.call(command, shell=True)
但是我希望在内存中执行此操作,避免将输入和输出文件保存到磁盘上。
我已找到以下代码来执行此操作(https://stackoverflow.com/questions/11301863/passing-pythons-file-like-object-to-ffmpeg-via-subprocess):
command = ['ffmpeg', '-y', '-i', '-', '-f', 'wav', '-']
process = subprocess.Popen(command, stdin=subprocess.PIPE)
wav, errordata = process.communicate(file)
但我在我的情况下遇到了困难。
我在服务器上作为multipart/form-data请求的一部分接收文件。
@server.route("/api/getText", methods=["POST"])
def api():
if "multipart/form-data" not in request.content_type:
return Response("invalid content type: {}".format(request.content_type))
# 检查文件格式
file = request.files['file']
if file:
print('**found file', file.filename)
现在我有一个FileStorage对象(https://tedboy.github.io/flask/generated/generated/werkzeug.FileStorage.html)。这个对象有一个流,可以使用read方法访问。因此,我认为我可以将其用作ffmpeg的输入,如下所示:
f = file.read()
command = ['ffmpeg', '-y', '-i', '-', '-f', 'wav', '-']
process = subprocess.Popen(command, stdin=subprocess.PIPE)
wav, errordata = process.communicate(f)
然而,这会产生以下错误:
AssertionError: Given audio file must be a filename string or a file-like object
我还尝试了另一种方法,我在网上找到的,使用io.BytesIO,但我无法再找到来源:
memfile = io.BytesIO() # 创建文件对象
memfile.write(file.read()) # 在文件对象中写入数据
memfile.seek(0) # 移动到开头以便从开头读取
然后再次尝试:
command = ['ffmpeg', '-y', '-i', '-', '-f', 'wav', '-']
process = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
wav, errordata = process.communicate(memfile)
这给我带来了以下错误:
TypeError: a bytes-like object is required, not '_io.BytesIO'
有人有想法如何做到这一点吗?
更新
第一个错误消息实际上不是由ffmpeg抛出的错误消息。正如v25在他的答案中正确指出的那样,第一个方法也返回一个字节对象,也是一个有效的解决方案。
错误消息是由一个库(speech_recognition)在尝试处理修改后的文件时抛出的。如果有人在同样的问题上遇到了这个问题,这里是解决方法:
ffmpeg返回的字节对象(变量wav)必须转换为类似文件的对象,正如错误消息所暗示的那样。可以像这样轻松地完成:
memfileOutput = io.BytesIO(wav)
英文:
I have successfully managed to use ffmpeg in python to convert the format of some audio files like this:
command = "ffmpeg -i audio.wav -vn -acodec pcm_s16le output.wav"
subprocess.call(command, shell=True)
However I want to do this in memory and avoid saving the input and output files to disk.
I have found the follwing code to do such a thing (https://stackoverflow.com/questions/11301863/passing-pythons-file-like-object-to-ffmpeg-via-subprocess):
command = ['ffmpeg', '-y', '-i', '-', '-f', 'wav', '-']
process = subprocess.Popen(command, stdin=subprocess.PIPE)
wav, errordata = process.communicate(file)
But I am struggeling to use this in my context.
I am receiving the file on a server as part of a multipart/form-data request.
@server.route("/api/getText", methods=["POST"])
def api():
if "multipart/form-data" not in request.content_type:
return Response("invalid content type: {}".format(request.content_type))
# check file format
file = request.files['file']
if file:
print('**found file', file.filename)
Now I have the file as a FileStorage Object (https://tedboy.github.io/flask/generated/generated/werkzeug.FileStorage.html). This object has a stream, which can be accessed by using the read method. So I thought I might be able to use this as input for ffmpeg like so:
f = file.read()
command = ['ffmpeg', '-y', '-i', '-', '-f', 'wav', '-']
process = subprocess.Popen(command, stdin=subprocess.PIPE)
wav, errordata = process.communicate(f)
However this yields the the following error:
AssertionError: Given audio file must be a filename string or a file-like object
I have also tried another approach which I found online, using io.BytesIO, to which I can't find the source anymore:
memfile = io.BytesIO() # create file-object
memfile.write(file.read()) # write in file-object
memfile.seek(0) # move to beginning so it will read from beginning
And then trying this again:
command = ['ffmpeg', '-y', '-i', '-', '-f', 'wav', '-']
process = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
wav, errordata = process.communicate(memfile)
This gets me the following error:
TypeError: a bytes-like object is required, not '_io.BytesIO'
Does anyone have an idea of how to do this?
Update
The first error message is actually not a error message thrown by ffmpeg. As v25 pointed out correctly in his answer the first approach also returns a bytes object and is also a valid solution.
The error message got thrown by a library (speech_recognition) when trying to work with the modified file. In the unlikely case of someone coming across the same problem here the solution:
The bytes objected returned by ffmpeg (variable wav) has to be turned into a file-like object as the error message implies. This can easily be done like this:
memfileOutput = io.BytesIO(wav)
答案1
得分: 2
根据您的评论,这似乎已经得到修复:
wav, errordata = process.communicate(memfile.read())
我不完全确定为什么在这里传递 f
不起作用,因为:
import io
print('file.read()', type(file.read()))
memfile = io.BytesIO()
memfile.write(file.read())
memfile.seek(0)
print('memfile', type(memfile))
print('memfile.read()', type(memfile.read()))
输出如下:
file.read() <class 'bytes'> # (f)
memfile <class '_io.BytesIO'>
memfile.read() <class 'bytes'>
所以在您的情况下,似乎 f
和 memfile.read()
的类型是相同的。
我实际上不确定为什么前者会导致 AssertionError: 给定的音频文件必须是文件名字符串或类似文件的对象
。
英文:
As per your comment, this appears to have been fixed by:
wav, errordata = process.communicate(memfile.read())
I'm not 100% sure on why passing f
here wouldn't work, because:
import io
print ('file.read()', type(file.read()))
memfile = io.BytesIO()
memfile.write(file.read())
memfile.seek(0)
print ('memfile', type(memfile))
print ('memfile.read()', type(memfile.read()))
gives...
file.read() <class 'bytes'> # (f)
memfile <class '_io.BytesIO'>
memfile.read() <class 'bytes'>
So it appears f
and memfile.read()
in your case are of the same type.
I'm not actually sure why the former would yield, AssertionError: Given audio file must be a filename string or a file-like object
.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论