| import os | |
| from typing import BinaryIO | |
| from fastapi import FastAPI, HTTPException, Request, status | |
| from fastapi.responses import StreamingResponse | |
| from scripts.iib.tool import get_video_type | |
| video_file_handler = {} | |
| def close_video_file_reader(path): | |
| if not get_video_type(path): | |
| return | |
| try: | |
| video_file_handler[path].close() | |
| except Exception as e: | |
| print(f"close file error: {e}") | |
| def send_bytes_range_requests( | |
| file_path, 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 open(file_path, mode="rb") as f: | |
| video_file_handler[file_path] = 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(file_path, start, end), | |
| headers=headers, | |
| status_code=status_code, | |
| ) | |