Python - Django:使用 HttpResponse 流式传输视频/mp4 文件

Posted

技术标签:

【中文标题】Python - Django:使用 HttpResponse 流式传输视频/mp4 文件【英文标题】:Python - Django: Streaming video/mp4 file using HttpResponse 【发布时间】:2016-01-17 11:18:58 【问题描述】:

我正在使用 Python2.7、django==1.7uwsgi 将视频/mp4 文件流式传输到 iPhone 播放器。

我的代码如下:

def stream(request):
     with open('/path/video.mp4', 'r') as video_file:
        response = HttpResponse(video_file.read(), content_type='video/mp4')
        response['Content-Disposition'] = 'inline; filename=%s' % 'video.mp4'
        return response
     video_file.close

当我使用一些小视频(小于 1MB)时,它会在浏览器中流式传输,但在 iPhone palyer 中出现此错误:

[uwsgi-http 密钥:127.0.0.1:8008 client_addr:192.168.0.172 client_port: 14563] hr_write(): 损坏的管道 [plugins/http/http.c 行 564]

当视频大小超过 5MB 时,它不会在两个(意味着浏览器和 iPhone 播放器)中流式传输,并出现相同的错误。

我尝试通过使用 StreamHttpRespose 返回的块块来做到这一点,如下所示:

def read(chunksize=8192):
    with open('/path/video.mp4', 'rb') as video_file:
        byte = video_file.read(chunksize)
        while byte:
            yield byte

return StreamingHttpResponse(read(), content_type='video/mp4')

但是有同样的错误:Broken pipe

仅供参考,我可以流式传输 pdf 和图像文件。此问题仅与 mp4 文件有关。而且我将 content_type 更改为“video-mpeg”,浏览器下载了它,而我想阻止文件下载。

你的想法是什么?有什么解决办法!!?

【问题讨论】:

为了流式传输,您需要另一个线程将数据写入响应。因为你这样做的方式很简单,直到你读完整个文件并一起发送。 @BogdanIulianBursuc 感谢您的评论,但在第二个解决方案(StreamHttpResponse)中,我将视频文件作为字节读取并通过 yield 命令在每个块中返回。这意味着它不需要等待获取整个文件。 嗨,Aida,我很想知道您是否找到了有关此主题的任何解决方案。我也有同样的问题:)谢谢 @Charlie,我向你解释了我为此做了什么。但这不是解决方案,只是它可以正常工作。如果您找到任何解决方案,请回答我的问题并获得您的分数;) 感谢@Aida.Mirabadi! 【参考方案1】:

我遇到了同样的问题,在找到可行的解决方案之前做了很多挖掘工作!

显然,html5 视频控件需要 Accept Ranges 标头才能工作 (https://***.com/a/24977085/4264463)。因此,我们需要解析来自HTTP_RANGE 的请求范围并返回Content-Range 和响应。传递给StreamingHttpResponse 的生成器也需要根据这个范围返回内容(offsetlength)。我发现以下 sn-p 效果很好(来自http://codegist.net/snippet/python/range_streamingpy_dcwatson_python):

import os
import re
import mimetypes
from wsgiref.util import FileWrapper

from django.http.response import StreamingHttpResponse


range_re = re.compile(r'bytes\s*=\s*(\d+)\s*-\s*(\d*)', re.I)


class RangeFileWrapper(object):
    def __init__(self, filelike, blksize=8192, offset=0, length=None):
        self.filelike = filelike
        self.filelike.seek(offset, os.SEEK_SET)
        self.remaining = length
        self.blksize = blksize

    def close(self):
        if hasattr(self.filelike, 'close'):
            self.filelike.close()

    def __iter__(self):
        return self

    def __next__(self):
        if self.remaining is None:
            # If remaining is None, we're reading the entire file.
            data = self.filelike.read(self.blksize)
            if data:
                return data
            raise StopIteration()
        else:
            if self.remaining <= 0:
                raise StopIteration()
            data = self.filelike.read(min(self.remaining, self.blksize))
            if not data:
                raise StopIteration()
            self.remaining -= len(data)
            return data


def stream_video(request, path):
    range_header = request.META.get('HTTP_RANGE', '').strip()
    range_match = range_re.match(range_header)
    size = os.path.getsize(path)
    content_type, encoding = mimetypes.guess_type(path)
    content_type = content_type or 'application/octet-stream'
    if range_match:
        first_byte, last_byte = range_match.groups()
        first_byte = int(first_byte) if first_byte else 0
        last_byte = int(last_byte) if last_byte else size - 1
        if last_byte >= size:
            last_byte = size - 1
        length = last_byte - first_byte + 1
        resp = StreamingHttpResponse(RangeFileWrapper(open(path, 'rb'), offset=first_byte, length=length), status=206, content_type=content_type)
        resp['Content-Length'] = str(length)
        resp['Content-Range'] = 'bytes %s-%s/%s' % (first_byte, last_byte, size)
    else:
        resp = StreamingHttpResponse(FileWrapper(open(path, 'rb')), content_type=content_type)
        resp['Content-Length'] = str(size)
    resp['Accept-Ranges'] = 'bytes'
    return resp

【讨论】:

你能举个例子说明如何在浏览器中做到这一点 @JosephDaudi HTML5 视频将自动从与stream_video 关联的端点流式传输数据。所以,我在我的urls.py 文件中使用了stream_video 作为一个视图,然后有了像这样的html:&lt;video width="320" height="240" controls&gt;&lt;source src="the/full/url/to/video.mp4" type="video/mp4"&gt;&lt;/video&gt; 你拯救了我的一天!谢谢! @KevinLee 在视图中,没有对 html 文件的引用。而且我认为您需要一个带有orvideo html 标记的视图,但不是两者兼而有之。 谢谢你,凯文。我正在写一个学校项目,你的回答很有帮助!【参考方案2】:

经过大量搜索,我没有找到我的解决方案。

所以,我尝试使用来自html5-video-streamer.js 参考的nodejs 轻松创建流服务器,如下所示:

var http       = require('http'),
    fs         = require('fs'),
    url        = require('url'),
    basePath   = '/var/www/my_project/media/',
    baseUrl    = 'Your Domain or IP',
    basePort   = 8081;

http.createServer(function (req, res) 

    // Get params from request.
    var params    = url.parse(req.url, true).query, 
        filePath  = basePath + params.type + '/' + params.name,
        stat      = fs.statSync(filePath),
        total     = stat.size;

      if (req.headers['range']) 
        var range         = req.headers.range,
            parts         = range.replace(/bytes=/, "").split("-"),
            partialstart  = parts[0],
            partialend    = parts[1],
            start         = parseInt(partialstart, 10),
            end           = partialend ? parseInt(partialend, 10) : total-1,
            chunksize     = (end-start)+1;

        var file = fs.createReadStream(filePath, start: start, end: end);
        res.writeHead(206,  'Content-Range'  : 'bytes ' + start + '-' + end + '/' + total,
                             'Accept-Ranges'  : 'bytes',
                             'Content-Length' : chunksize,
                             'Content-Type'   : 'video/mp4' );
        file.pipe(res);

        // Close file at end of stream.
        file.on('end', function()
          file.close();
        );
       
      else 
        res.writeHead(206,  'Content-Length'   : total,
                             'Content-Type'     : 'video/mp4' );

        var file = fs.createReadStream(filePath);
        file.pipe(res);

        // Close file at end of stream.
        file.on('end', function()
          file.close();
        );
      
 ).listen(basePort, baseUrl);

现在我有一个单独的流服务器和nodejs,它在提供我的API的python项目旁边流mp4文件。

我知道这不是我的解决方案,但它对我有用 ;)

【讨论】:

实际上,我已经在使用 nodejs 服务器通过 websocket 流式传输我们的视频。但是错误仍然存​​在:( @Charlie 我使用“createReadStream”方法来传输 mp4 文件而不是 websocket。我会把我的代码放在我的答案中。 非常感谢。但我不确定这是否能满足我的需求。我们没有使用 HttpResponse:我们使用带有“ws://serverip:port”的 videocanvas 在网络浏览器中显示视频 @Charlie 好的,知道了。所以,让我们也看看这个链接 => binaryjs.com 。最后,如果您找到解决方案,请回答我的问题。谢谢。 再次感谢!这看起来真的很好。找到解决方案后,我一定会尽快为您提供答案:)

以上是关于Python - Django:使用 HttpResponse 流式传输视频/mp4 文件的主要内容,如果未能解决你的问题,请参考以下文章

使用 Python 和 Django 搭建 Web 应用

python django能开发大型网站吗

django-1-新手如何使用django

Python-Django使用MemcachedCache缓存

Python开发之Django框架入门Django安装

python3使用Django框架连接mysql(python3+Django+MySQL+pymysql)