【问题标题】:Streaming video files using Flask使用 Flask 流式传输视频文件
【发布时间】:2022-04-14 01:54:41
【问题描述】:

请帮我理解一下。

我正在尝试让 Flask 流式传输 .mp4 视频。我知道我可以使用Response(generator_function())

但在浏览器中观看视频时不允许跳转到特定分钟​​。

所以我正在尝试使用 Range 标头。这是我尝试的方法:

app = Flask(__name__)


def get_chunk(byte1=None, byte2=None):
    filesize = os.path.getsize('try2.mp4')
    yielded = 0
    yield_size = 1024 * 1024

    if byte1 is not None:
        if not byte2:
            byte2 = filesize
        yielded = byte1
        filesize = byte2

    with open('try2.mp4', 'rb') as f:
        content = f.read()

    while True:
        remaining = filesize - yielded
        if yielded == filesize:
            break
        if remaining >= yield_size:
            yield content[yielded:yielded+yield_size]
            yielded += yield_size
        else:
            yield content[yielded:yielded+remaining]
            yielded += remaining


@app.route('/')
def get_file():
    filesize = os.path.getsize('try2.mp4')
    range_header = flask_request.headers.get('Range', None)

    if range_header:
        byte1, byte2 = None, None
        match = re.search(r'(\d+)-(\d*)', range_header)
        groups = match.groups()

        if groups[0]:
            byte1 = int(groups[0])
        if groups[1]:
            byte2 = int(groups[1])

        if not byte2:
            byte2 = byte1 + 1024 * 1024
            if byte2 > filesize:
                byte2 = filesize

        length = byte2 + 1 - byte1

        resp = Response(
            get_chunk(byte1, byte2),
            status=206, mimetype='video/mp4',
            content_type='video/mp4',
            direct_passthrough=True
        )

        resp.headers.add('Content-Range',
                         'bytes {0}-{1}/{2}'
                         .format(byte1,
                                 length,
                                 filesize))
        return resp

    return Response(
        get_chunk(),
        status=200, mimetype='video/mp4'
    )


@app.after_request
def after_request(response):
    response.headers.add('Accept-Ranges', 'bytes')
    return response

get_chunk 如果指定了此字节,则生成从 byte1 到 byte2 的块,否则从 0 到文件大小(块大小 = 1MB)。

但它不起作用。 我看到首先浏览器发送具有 状态的请求。然后使用 。请告诉我如何使它工作。

【问题讨论】:

    标签: python flask


    【解决方案1】:

    在开发服务器上,您需要启用threaded=True 才能使视频流正常工作。

    更新:

    @app.after_request
    def after_request(response):
        response.headers.add('Accept-Ranges', 'bytes')
        return response
    
    
    def get_chunk(byte1=None, byte2=None):
        full_path = "try2.mp4"
        file_size = os.stat(full_path).st_size
        start = 0
        
        if byte1 < file_size:
            start = byte1
        if byte2:
            length = byte2 + 1 - byte1
        else:
            length = file_size - start
    
        with open(full_path, 'rb') as f:
            f.seek(start)
            chunk = f.read(length)
        return chunk, start, length, file_size
    
    
    @app.route('/video')
    def get_file():
        range_header = request.headers.get('Range', None)
        byte1, byte2 = 0, None
        if range_header:
            match = re.search(r'(\d+)-(\d*)', range_header)
            groups = match.groups()
    
            if groups[0]:
                byte1 = int(groups[0])
            if groups[1]:
                byte2 = int(groups[1])
           
        chunk, start, length, file_size = get_chunk(byte1, byte2)
        resp = Response(chunk, 206, mimetype='video/mp4',
                          content_type='video/mp4', direct_passthrough=True)
        resp.headers.add('Content-Range', 'bytes {0}-{1}/{2}'.format(start, start + length - 1, file_size))
        return resp
    
    if __name__ == '__main__':
        app.run(threaded=True)
    

    【讨论】:

    • 感谢您的回答。但我需要使用我的自定义函数来生成块。 send_file() 只接受文件名.. 我需要它来获取生成器函数。
    • 它做同样的事情,以块的形式推送数据(但你不能设置块大小),当你寻找视频时,它会在请求中向服务器发送范围,服务器以 http 代码 @987654324 响应@。自己试试。如果您设置app.run(threaded=True),您的代码将起作用
    • 对不起,我记得,如果文件不完整,send_file() 会失败。例如,我从某个来源收到一个文件,客户端应该等到文件被下载。使用自定义生成器功能,我可以为此添加一些逻辑。我的意思是客户端想要视频文件的确切分钟,但这些字节不可用(即使在服务器上)的情况。
    • 阅读this answer
    • 我阅读了您链接的答案。稍微更改了我的代码,但仍然无法获得结果。我编辑了我的代码。我需要我的代码作为 send_file 函数工作,但正如我所说我不能使用 send_file 因为我需要自定义块产生(例如更改块大小或添加那里有一些额外的逻辑)。如果服务器上的文件不完整且正在上传,send_file 也将不起作用。
    【解决方案2】:

    好的,我可能会迟到,但这是我编写的简化代码。仍然与上面相同的概念,但我认为更好更简单。

    import os
    import re
    from flask import render_template, request, Blueprint, current_app, send_file
    
    core = Blueprint("core", __name__)
    
    # your request handles here with @core.route()
    
    
    @core.route("/")
    def home():
        return render_template("index.html")
    
    
    @core.route("/video", methods=["GET"])
    def video():
        headers = request.headers
        if not "range" in headers:
            return current_app.response_class(status=400)
    
        video_path = os.path.abspath(os.path.join("media", "test.mp4"))
        size = os.stat(video_path)
        size = size.st_size
    
        chunk_size = 10**3
        start = int(re.sub("\D", "", headers["range"]))
        end = min(start + chunk_size, size - 1)
    
        content_lenght = end - start + 1
    
        def get_chunk(video_path, start, end):
            with open(video_path, "rb") as f:
                f.seek(start)
                chunk = f.read(end)
            return chunk
    
        headers = {
            "Content-Range": f"bytes {start}-{end}/{size}",
            "Accept-Ranges": "bytes",
            "Content-Length": content_lenght,
            "Content-Type": "video/mp4",
        }
    
        return current_app.response_class(get_chunk(video_path, start, end), 206, headers)
    
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-10-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-08-22
      相关资源
      最近更新 更多