Spaces:
Running
Running
| import os | |
| import re | |
| import hashlib | |
| from starlette.responses import Response | |
| from starlette.types import Receive, Scope, Send | |
| class RangeFileResponse(Response): | |
| def __init__( | |
| self, | |
| path: str, | |
| media_type: str = "video/mp4", | |
| content_disposition_type: str = "inline", | |
| filename: str | None = None, | |
| ): | |
| self.path = path | |
| self.file_size = os.path.getsize(path) | |
| self.media_type = media_type | |
| self.content_disposition_type = content_disposition_type | |
| self.filename = filename or os.path.basename(path) | |
| stat = os.stat(path) | |
| self.etag = f'"{hashlib.md5(f"{stat.st_mtime}-{stat.st_size}".encode()).hexdigest()}"' | |
| self.last_modified = stat.st_mtime | |
| super().__init__() | |
| async def __call__(self, scope: Scope, receive: Receive, send: Send): | |
| if scope.get("method") == "HEAD": | |
| headers = { | |
| "content-type": self.media_type, | |
| "content-disposition": f'{self.content_disposition_type}; filename="{self.filename}"', | |
| "accept-ranges": "bytes", | |
| "cache-control": "private, max-age=3600", | |
| "etag": self.etag, | |
| "content-length": str(self.file_size), | |
| } | |
| await send({ | |
| "type": "http.response.start", | |
| "status": 200, | |
| "headers": [(k.encode(), v.encode()) for k, v in headers.items()], | |
| }) | |
| await send({"type": "http.response.body", "body": b"", "more_body": False}) | |
| return | |
| request_headers = dict( | |
| (k.decode().lower(), v.decode()) for k, v in scope.get("headers", []) | |
| ) | |
| range_header = request_headers.get("range", "") | |
| headers = { | |
| "content-type": self.media_type, | |
| "content-disposition": f'{self.content_disposition_type}; filename="{self.filename}"', | |
| "accept-ranges": "bytes", | |
| "cache-control": "private, max-age=3600", | |
| "etag": self.etag, | |
| } | |
| if range_header: | |
| range_match = re.match(r"bytes=(\d*)-(\d*)", range_header) | |
| if range_match: | |
| start_str, end_str = range_match.groups() | |
| if not start_str and not end_str: | |
| headers["content-range"] = f"bytes */{self.file_size}" | |
| headers["content-length"] = "0" | |
| await send({ | |
| "type": "http.response.start", | |
| "status": 416, | |
| "headers": [ | |
| (k.encode(), v.encode()) for k, v in headers.items() | |
| ], | |
| }) | |
| await send({"type": "http.response.body", "body": b"", "more_body": False}) | |
| return | |
| if start_str and end_str: | |
| start = int(start_str) | |
| end = int(end_str) | |
| elif start_str: | |
| start = int(start_str) | |
| end = self.file_size - 1 | |
| else: | |
| suffix_len = int(end_str) | |
| start = max(0, self.file_size - suffix_len) | |
| end = self.file_size - 1 | |
| if start >= self.file_size: | |
| headers["content-range"] = f"bytes */{self.file_size}" | |
| headers["content-length"] = "0" | |
| await send({ | |
| "type": "http.response.start", | |
| "status": 416, | |
| "headers": [ | |
| (k.encode(), v.encode()) for k, v in headers.items() | |
| ], | |
| }) | |
| await send({"type": "http.response.body", "body": b"", "more_body": False}) | |
| return | |
| end = min(end, self.file_size - 1) | |
| content_length = end - start + 1 | |
| headers["content-range"] = f"bytes {start}-{end}/{self.file_size}" | |
| headers["content-length"] = str(content_length) | |
| await send({ | |
| "type": "http.response.start", | |
| "status": 206, | |
| "headers": [ | |
| (k.encode(), v.encode()) for k, v in headers.items() | |
| ], | |
| }) | |
| chunk_size = 1024 * 1024 # 1MB chunks for better streaming performance | |
| with open(self.path, "rb") as f: | |
| f.seek(start) | |
| remaining = content_length | |
| while remaining > 0: | |
| read_size = min(chunk_size, remaining) | |
| data = f.read(read_size) | |
| if not data: | |
| break | |
| remaining -= len(data) | |
| await send({ | |
| "type": "http.response.body", | |
| "body": data, | |
| "more_body": remaining > 0, | |
| }) | |
| return | |
| headers["content-length"] = str(self.file_size) | |
| await send({ | |
| "type": "http.response.start", | |
| "status": 200, | |
| "headers": [ | |
| (k.encode(), v.encode()) for k, v in headers.items() | |
| ], | |
| }) | |
| chunk_size = 1024 * 1024 # 1MB chunks for better streaming performance | |
| with open(self.path, "rb") as f: | |
| while True: | |
| data = f.read(chunk_size) | |
| if not data: | |
| break | |
| more = bool(f.tell() < self.file_size) | |
| await send({ | |
| "type": "http.response.body", | |
| "body": data, | |
| "more_body": more, | |
| }) | |