mediapi/mediapi/media.py
Jean-Michel Tremblay b8cd65b8ca Initial mediapi: WiFi AP + media browser with HDMI playback control
Raspberry Pi 4 appliance that boots as a WiFi access point and serves a
phone-controlled web app for browsing /localmedia and driving video
playback out the Pi's HDMI port.

- NetworkManager-based AP setup (documented in README)
- Flask app + vanilla HTML/JS single-page UI (Control / Playback modes)
- mpv persistent daemon (DRM/KMS HDMI output) controlled over JSON IPC
- long-lived session-cookie auth
- "keep playing" folder auto-advance, including when started mid-folder
- systemd units for both mpv and the app, autostart on boot

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 21:45:57 -04:00

70 lines
2.4 KiB
Python

import os
class PathError(Exception):
pass
def _resolved_roots(media_roots):
return [os.path.realpath(r) for r in media_roots]
def resolve_path(requested_path, media_roots):
"""Resolve requested_path (absolute or relative to a root) and ensure it
stays within one of the configured media roots. Raises PathError otherwise."""
roots = _resolved_roots(media_roots)
if not requested_path:
# no path given -> the roots themselves are the top-level listing
return None
real = os.path.realpath(requested_path)
for root in roots:
if real == root or real.startswith(root + os.sep):
return real
raise PathError(f"path escapes configured media roots: {requested_path}")
def list_directory(path, media_roots, video_extensions):
"""Return {"folders": [...], "files": [...]} for the given resolved path,
or for the top-level roots themselves when path is None."""
if path is None:
folders = []
for root in media_roots:
real = os.path.realpath(root)
if os.path.isdir(real):
folders.append({"name": os.path.basename(real) or real, "path": real})
folders.sort(key=lambda e: e["name"].lower())
return {"folders": folders, "files": []}
folders = []
files = []
with os.scandir(path) as it:
for entry in it:
if entry.name.startswith("."):
continue
if entry.is_dir(follow_symlinks=True):
folders.append({"name": entry.name, "path": entry.path})
elif entry.is_file(follow_symlinks=True):
ext = os.path.splitext(entry.name)[1].lower()
if ext in video_extensions:
files.append({"name": entry.name, "path": entry.path})
folders.sort(key=lambda e: e["name"].lower())
files.sort(key=lambda e: e["name"].lower())
return {"folders": folders, "files": files}
def list_video_files(path, video_extensions):
"""Sorted list of video file paths directly inside `path` (no recursion)."""
files = []
with os.scandir(path) as it:
for entry in it:
if entry.name.startswith("."):
continue
if entry.is_file(follow_symlinks=True):
ext = os.path.splitext(entry.name)[1].lower()
if ext in video_extensions:
files.append(entry.path)
files.sort(key=lambda p: os.path.basename(p).lower())
return files