Streaming video files using Flask
Question:
Please help me to understand one moment.
I am trying to make Flask to stream .mp4 video. I know that i can use Response(generator_function())
But it does not allow to jump to specific minute while watching a video in browser.
So i am trying to use Range header. Here is how i try it:
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 yields chunks from byte1 to byte2 if this bytes are specified, and from 0 to filesize otherwise (chunk size = 1MB).
But it does not work.
I see that firstly browser sends request with <200> status. And then with <206>. Please advice me how to make it working.
Answers:
On development server you need to enable threaded=True
for video stream to work correctly.
Updated:
import os
import re
...
@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)
okay i this might be coming late but this is a simplified code i wrote. still same concept as above but better and simpler i think.
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 ** 6) * 3 #1000kb makes 1mb * 3 = 3mb (this is based on your choice)
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, chunk_size):
with open(video_path, "rb") as f:
f.seek(start)
chunk = f.read(chunk_size)
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,chunk_size), 206, headers)
Please help me to understand one moment.
I am trying to make Flask to stream .mp4 video. I know that i can use Response(generator_function())
But it does not allow to jump to specific minute while watching a video in browser.
So i am trying to use Range header. Here is how i try it:
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 yields chunks from byte1 to byte2 if this bytes are specified, and from 0 to filesize otherwise (chunk size = 1MB).
But it does not work.
I see that firstly browser sends request with <200> status. And then with <206>. Please advice me how to make it working.
On development server you need to enable threaded=True
for video stream to work correctly.
Updated:
import os
import re
...
@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)
okay i this might be coming late but this is a simplified code i wrote. still same concept as above but better and simpler i think.
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 ** 6) * 3 #1000kb makes 1mb * 3 = 3mb (this is based on your choice)
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, chunk_size):
with open(video_path, "rb") as f:
f.seek(start)
chunk = f.read(chunk_size)
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,chunk_size), 206, headers)