Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python - Django: Streaming video/mp4 file using HttpResponse

I'm using Python2.7, django==1.7 and uwsgi for streaming video/mp4 file to iPhone player.

My code is as below:

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

When i use some small video (less than 1MB), it streams in browser, but in iPhone palyer i have this error:

[uwsgi-http key: 127.0.0.1:8008 client_addr: 192.168.0.172 client_port: 14563] hr_write(): Broken pipe [plugins/http/http.c line 564]

And when the video size is more that 5MB, it doesn't stream in both (means browser and iPhone player) with same error.

I tried to do that by chunk chunk returning using StreamHttpRespose as below:

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

But there is the same error: Broken pipe.

fyi I can stream pdf and image files. This problem is only with mp4 files. And also i changed the content_type to 'video-mpeg', the browser downloaded that, while i want to prevent file downloading.

What's your idea? Any solution!!?

like image 570
Aida.Mirabadi Avatar asked Oct 19 '15 07:10

Aida.Mirabadi


2 Answers

I had the same problem and did a lot of digging before finding a workable solution!

Apparently the Accept Ranges header is needed for HTML5 video controls to work (https://stackoverflow.com/a/24977085/4264463). So, we need to both parse the requested range from HTTP_RANGE and return Content-Range with the response. The generator that is passed to StreamingHttpResponse also needs to return content based on this range as well (by offset and length). I've found the follow snippet that works great (from 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
like image 154
Kevin Lee Avatar answered Oct 20 '22 14:10

Kevin Lee


After a lot of search, i didn't find my solution.

So, i tried to create a stream-server easily using nodejs from html5-video-streamer.js reference as below:

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

Now i have separate stream-server with nodejs that streams mp4 files beside python project that provides my APIs.

I'm aware It's not my solution, but it works for me ;)

like image 30
Aida.Mirabadi Avatar answered Oct 20 '22 12:10

Aida.Mirabadi