feat: videoplayer toegevoegd

This commit is contained in:
kodi
2026-03-12 10:37:06 +01:00
parent 5123067100
commit 8c2fbfef74
14 changed files with 593 additions and 3 deletions
@@ -25,6 +25,10 @@ SPECIAL_TEXT_FILENAMES = {
"dockerfile": "text/plain",
"containerfile": "text/plain",
}
VIDEO_CONTENT_TYPES = {
".mp4": "video/mp4",
".mkv": "video/x-matroska",
}
class FileOpsService:
@@ -302,6 +306,53 @@ class FileOpsService:
modified=saved["modified"],
)
def prepare_video_stream(self, path: str, range_header: str | None = None) -> dict:
resolved_target = self._path_guard.resolve_existing_path(path)
if resolved_target.absolute.is_dir():
raise AppError(
code="type_conflict",
message="Source must be a file",
status_code=409,
details={"path": resolved_target.relative},
)
if not resolved_target.absolute.is_file():
raise AppError(
code="type_conflict",
message="Unsupported path type for video",
status_code=409,
details={"path": resolved_target.relative},
)
content_type = self._video_content_type_for(resolved_target.absolute)
if content_type is None:
raise AppError(
code="unsupported_type",
message="File type is not supported for video playback",
status_code=409,
details={"path": resolved_target.relative},
)
file_size = int(resolved_target.absolute.stat().st_size)
start = 0
end = max(file_size - 1, 0)
status_code = 200
headers = {"Accept-Ranges": "bytes"}
if range_header:
start, end = self._parse_range_header(range_header, file_size)
status_code = 206
headers["Content-Range"] = f"bytes {start}-{end}/{file_size}"
headers["Content-Length"] = str(max((end - start) + 1, 0))
return {
"status_code": status_code,
"headers": headers,
"content_type": content_type,
"content": self._filesystem.stream_file_range(resolved_target.absolute, start, end),
}
@staticmethod
def _join_relative(base: str, name: str) -> str:
return f"{base}/{name}" if base else name
@@ -313,6 +364,10 @@ class FileOpsService:
return special_name
return TEXT_CONTENT_TYPES.get(path.suffix.lower())
@staticmethod
def _video_content_type_for(path: Path) -> str | None:
return VIDEO_CONTENT_TYPES.get(path.suffix.lower())
def _record_history(
self,
*,
@@ -363,3 +418,43 @@ class FileOpsService:
from datetime import datetime, timezone
return datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
@staticmethod
def _parse_range_header(range_header: str, file_size: int) -> tuple[int, int]:
def invalid_range() -> AppError:
return AppError(
code="invalid_request",
message="Invalid Range header",
status_code=400,
)
if not range_header.startswith("bytes="):
raise invalid_range()
value = range_header[len("bytes="):].strip()
if "," in value or "-" not in value:
raise invalid_range()
start_text, end_text = value.split("-", 1)
if start_text == "" and end_text == "":
raise invalid_range()
try:
if start_text == "":
suffix_length = int(end_text)
if suffix_length <= 0:
raise invalid_range()
if suffix_length >= file_size:
return 0, max(file_size - 1, 0)
return file_size - suffix_length, file_size - 1
start = int(start_text)
if start < 0 or start >= file_size:
raise invalid_range()
if end_text == "":
return start, file_size - 1
end = int(end_text)
if end < start:
raise invalid_range()
return start, min(end, file_size - 1)
except ValueError as exc:
raise invalid_range() from exc