当无法播放 Flask 文件中的流式响应时

When streaming response in Flask file unplayable

我目前有一个功能,可以在来自 youtube 的 flv 流上 运行s ffmpeg enconder。

def console(cmd, add_newlines=False):
    p = Popen(cmd, shell=True, stdout=PIPE)
    while True:
        data = p.stdout.readline()
        if add_newlines:
            data += str('\n')
        yield data

        p.poll()
        if isinstance(p.returncode, int):
            if p.returncode > 0:
                # return code was non zero, an error?
                print 'error:', p.returncode
            break

当我 运行 ffmpeg 命令并将其输出到文件时,这工作正常。该文件可以播放。

mp3 = console('ffmpeg -i "%s" -acodec libmp3lame -ar 44100 -f mp3 test.mp3' % video_url, add_newlines=True)

但是当我通过 - 而不是 test.mp3 将 ffmpeg 输出到 stdout 并流式传输该响应时。文件流很好,大小正确。但不能正确播放。听起来很不稳定,当我检查文件的属性时,它没有像 test.mp3

那样显示它的数据
@app.route('/test.mp3')
def generate_large_mp3(path):
    mp3 = console('ffmpeg -i "%s" -acodec libmp3lame -ar 44100 -f mp3 -' % video_url, add_newlines=True)
    return Response(stream_with_context(mp3), mimetype="audio/mpeg3",
                   headers={"Content-Disposition":
                                "attachment;filename=test.mp3"})

有什么我遗漏的吗?

你有几个问题。您似乎从某处复制了此 console 函数。主要问题是此功能旨在处理文本输出。你是 运行 ffmpeg 的命令会将二进制原始 mp3 数据转储到标准输出,因此读取该输出就好像它是带有 readlines() 的文本文件一样将不起作用。 mp3二进制流中根本不存在行的概念。

我已经调整了您的 console() 函数以处理二进制流。这是我的版本:

def console(cmd):
    p = Popen(cmd, shell=True, stdout=PIPE)
    while True:
        data = p.stdout.read()
        yield data

        p.poll()
        if isinstance(p.returncode, int):
            if p.returncode > 0:
                # return code was non zero, an error?
                print 'error:', p.returncode
            break

请注意,我删除了所有对换行符处理的引用,并将 readline() 替换为 read()

有了这个版本,我就可以使用流媒体了。希望对您有所帮助!

Edit:您可以通过调用 read(N) 强制响应以指定大小的块发出,其中 N 是块大小。对于给定的音频比特率,您可以计算持续时间为 5 秒的音频块的字节大小,然后流式传输 5 秒块。

使用 flask 流式传输子进程生成的 mp3 内容:

#!/usr/bin/env python
import os
from functools import partial
from subprocess import Popen, PIPE

from flask import Flask, Response  # $ pip install flask

mp3file = 'test.mp3'
app = Flask(__name__)


@app.route('/')
def index():
    return """<!doctype html>
<title>Play {mp3file}</title>
<audio controls autoplay >
    <source src="{mp3file}" type="audio/mp3" >
    Your browser does not support this audio format.
</audio>""".format(mp3file=mp3file)


@app.route('/' + mp3file)
def stream():
    process = Popen(['cat', mp3file], stdout=PIPE, bufsize=-1)
    read_chunk = partial(os.read, process.stdout.fileno(), 1024)
    return Response(iter(read_chunk, b''), mimetype='audio/mp3')

if __name__ == "__main__":
    app.run()

['cat', mp3file] 替换为将 mp3 内容写入标准输出的 ffmpeg 命令。