I'm using the Fast API framework, I'm trying to use it as a stream type response to visualize the videos by loading parts.
@router.get("/video/{name_video}")
async def get_video(name_video: str, range: str = Header(None)):
# bytes=0-
start, end = range.replace("bytes=", "").split("-")
start = int(start)
end = int(start + PORTION_SIZE)
with open(current_directory + name_video, "rb") as myfile:
myfile.seek(start)
data = myfile.read(end - start)
size_video = str(os.path.getsize(current_directory + name_video))
headers = {
'Content-Range': f'bytes {str(start)}-{str(end)}/{size_video}',
'Accept-Ranges': 'bytes'
}
return Response(content=data, status_code=206, headers=headers, media_type="video/mp4")
It happens that it responds with blob data type and I try to use it in the video component as is:
<video width="1200" controls>
<source src="http://127.0.0.1:8000/api/stream_video/video_tet.mp4" type="video/mp4">
</video>
I have tried to use it in browsers like Google Chrome and Edge and they do not work, the curious thing is that with Mozilla FireFox if it works. if you have to use request using js and then insert it there would be no problem, the question is that I do not know how to do it.
I would like to know in general how it would work this way and if there is a charitable soul that optimizes the response using this procedure I would be very happy.
Thank you very much for your help.
It is better to use StreamingResponse
to make sure all browsers properly support the video stream you're creating. A very similar question has been asked and answered in the FastAPI Github issue tracker here.
There's an excellent answer posted by the user angel-langdon
which I'll copy here for reference. I've used it myself (slightly modifies as I use sshfs as data source) in my own project and it works great.
import os
from typing import BinaryIO
from fastapi import FastAPI, HTTPException, Request, status
from fastapi.responses import StreamingResponse
def send_bytes_range_requests(
file_obj: BinaryIO, start: int, end: int, chunk_size: int = 10_000
):
"""Send a file in chunks using Range Requests specification RFC7233
`start` and `end` parameters are inclusive due to specification
"""
with file_obj as f:
f.seek(start)
while (pos := f.tell()) <= end:
read_size = min(chunk_size, end + 1 - pos)
yield f.read(read_size)
def _get_range_header(range_header: str, file_size: int) -> tuple[int, int]:
def _invalid_range():
return HTTPException(
status.HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE,
detail=f"Invalid request range (Range:{range_header!r})",
)
try:
h = range_header.replace("bytes=", "").split("-")
start = int(h[0]) if h[0] != "" else 0
end = int(h[1]) if h[1] != "" else file_size - 1
except ValueError:
raise _invalid_range()
if start > end or start < 0 or end > file_size - 1:
raise _invalid_range()
return start, end
def range_requests_response(
request: Request, file_path: str, content_type: str
):
"""Returns StreamingResponse using Range Requests of a given file"""
file_size = os.stat(file_path).st_size
range_header = request.headers.get("range")
headers = {
"content-type": content_type,
"accept-ranges": "bytes",
"content-encoding": "identity",
"content-length": str(file_size),
"access-control-expose-headers": (
"content-type, accept-ranges, content-length, "
"content-range, content-encoding"
),
}
start = 0
end = file_size - 1
status_code = status.HTTP_200_OK
if range_header is not None:
start, end = _get_range_header(range_header, file_size)
size = end - start + 1
headers["content-length"] = str(size)
headers["content-range"] = f"bytes {start}-{end}/{file_size}"
status_code = status.HTTP_206_PARTIAL_CONTENT
return StreamingResponse(
send_bytes_range_requests(open(file_path, mode="rb"), start, end),
headers=headers,
status_code=status_code,
)
app = FastAPI()
@app.get("/video")
def get_video(request: Request):
return range_requests_response(
request, file_path="path_to_my_video.mp4", content_type="video/mp4"
)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With