feat(video): add streamable video events & dashboard controls
Add end-to-end support for video events: server streaming, scheduler metadata, API fields, and dashboard UI. - Server: range-capable streaming endpoint with byte-range support. - Scheduler: emits `video` object; best-effort HEAD probe adds `mime_type`, `size`, `accept_ranges`; placeholders for richer metadata (duration/resolution/bitrate/qualities/thumbnails). - API/DB: accept and persist `event_media_id`, `autoplay`, `loop`, `volume` for video events. - Frontend: Event modal supports video selection + playback options; FileManager increased upload size and client-side duration check (max 10 minutes). - Docs/UX: bumped program-info, added UX-only changelog and updated Copilot instructions for contributors. - Notes: metadata extraction (ffprobe), checksum persistence, and HLS/DASH transcoding are recommended follow-ups (separate changes).
This commit is contained in:
@@ -3,6 +3,8 @@ from server.database import Session
|
||||
from models.models import EventMedia
|
||||
import os
|
||||
|
||||
from flask import Response, abort, session as flask_session
|
||||
|
||||
# Blueprint for direct file downloads by media ID
|
||||
files_bp = Blueprint("files", __name__, url_prefix="/api/files")
|
||||
|
||||
@@ -66,3 +68,29 @@ def download_converted(relpath: str):
|
||||
if not os.path.isfile(abs_path):
|
||||
return jsonify({"error": "File not found"}), 404
|
||||
return send_from_directory(os.path.dirname(abs_path), os.path.basename(abs_path), as_attachment=True)
|
||||
|
||||
|
||||
@files_bp.route('/stream/<path:filename>')
|
||||
def stream_file(filename: str):
|
||||
"""Stream a media file via nginx X-Accel-Redirect after basic auth checks.
|
||||
|
||||
The nginx config must define an internal alias for /internal_media/ that
|
||||
points to the media folder (for example: /opt/infoscreen/server/media/).
|
||||
"""
|
||||
# Basic session-based auth: adapt to your project's auth logic if needed
|
||||
user_role = flask_session.get('role')
|
||||
if not user_role:
|
||||
return abort(403)
|
||||
|
||||
# Normalize path to avoid directory traversal
|
||||
safe_path = os.path.normpath('/' + filename).lstrip('/')
|
||||
abs_path = os.path.join(MEDIA_ROOT, safe_path)
|
||||
if not os.path.isfile(abs_path):
|
||||
return abort(404)
|
||||
|
||||
# Return X-Accel-Redirect header to let nginx serve the file efficiently
|
||||
internal_path = f'/internal_media/{safe_path}'
|
||||
resp = Response()
|
||||
resp.headers['X-Accel-Redirect'] = internal_path
|
||||
# Optional: set content-type if you want (nginx can detect it)
|
||||
return resp
|
||||
|
||||
Reference in New Issue
Block a user