How to use stream response to process video in Django

created at 07-17-2021 views: 1

description

Use the <video> tag of html5 to play:

<video width="320" height="240" controls>
 <source src="/static/video/demo.mp4" type="video/mp4">
 Your browser does not support the Video tag.
</video>

But in this way, the progress bar in the video cannot be used, and if it is returned as a static file, the background program will take up a lot of memory.

The use of response streams can solve these two problems.

StreamingHttpResponse

Most Django responses use HttpResponse. This means that the body of the response is built in memory and sent to the HTTP client as a single piece. If you use StreamingHttpResponse, you can return it in chunks (partial chunks). A very simple example is:

from django.http import StreamingHttpResponse

def hello():
  yield 'Hello,'
  yield 'there!'

def test(request):
  return StreamingHttpResponse(hello)

According to the WSGI protocol, when the server is called, the application object must return an iterable, resulting in zero or more byte strings. Therefore, we can complete the flow response function by providing a generator to the server.

The common use of StreamingHttpResponse is the downloading of some large files, etc., which can also be used to complete the function of resumable upload.

Video streaming

When using a video stream, you can get the starting number of bytes from the request header.

the starting number of bytes

This field seems to be automatically provided by the browser, because in the html code, I only need to change the src of the video from a static address to a routing method. For the response body, a range of blocks returned by the response body should also be provided:

 a range of blocks returned

  • Content-Range respectively represents the start byte number-the end byte number / the total bytes of the file, and the content of the response body contains the content within the range of the file. The code to process the video stream is as follows:
import re
import os
from wsgiref.util import FileWrapper
from django.http import StreamingHttpResponse

def file_iterator(file_name, chunk_size=8192, offset=0, length=None):
  with open(file_name, "rb") as f:
    f.seek(offset, os.SEEK_SET)
    remaining = length
    while True:
      bytes_length = chunk_size if remaining is None else min(remaining, chunk_size)
      data = f.read(bytes_length)
      if not data:
        break
      if remaining:
        remaining -= len(data)
      yield data

def stream_video(request, path):
  """Responding to the video file by streaming media"""
  range_header = request.META.get('HTTP_RANGE', '').strip()
  range_re = re.compile(r'bytes\s*=\s*(\d+)\s*-\s*(\d*)', re.I)
  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 = first_byte + 1024 * 1024 * 8    # 8M Each piece, the maximum volume of the response body
    if last_byte >= size:
      last_byte = size - 1
    length = last_byte - first_byte + 1
    resp = StreamingHttpResponse(file_iterator(path, 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:
    # When it is not obtained by video stream, the entire file is returned by generator to save memory
    resp = StreamingHttpResponse(FileWrapper(open(path, 'rb')), content_type=content_type)
    resp['Content-Length'] = str(size)
  resp['Accept-Ranges'] = 'bytes'
  return resp
Please log in to leave a comment.