mediapi/mediapi/config.py
Jean-Michel Tremblay afe1ab3e2f Replace mpv with Kodi as the player; mediapi drives it over JSON-RPC
Playback moves to Kodi (standalone on GBM/KMS -- the smooth, hardware-decoded
LibreELEC path). mediapi becomes a thin remote: it browses media and controls
Kodi over its JSON-RPC HTTP API. This drops every mpv/DRM/X-mirror problem
(DRM-master exclusivity, gpu-next "export failed" wedges, X-mirror A/V desync,
software-decode choppiness) -- none of which had a working single config.

- new mediapi/kodi.py (stdlib JSON-RPC client); player.py rewritten to poll
  and drive Kodi (Player.Open/PlayPause/Seek, Application.SetVolume,
  Player.GetProperties), same public interface + keep-playing auto-advance
- config.py: KODI_* settings replace MPV_SOCKET; __init__ + api_routes updated
- delete mpv_ipc.py, the mpv/X units, start-mpv/session scripts
- new systemd/mediapi-kodi.service.template (standalone Kodi on tty1) +
  scripts/configure-kodi.py (headlessly enable Kodi's JSON-RPC web server)
- install.sh now bootstraps a BARE Pi OS Lite end to end: apt base packages,
  installs uv, installs Kodi, enables its web API, configures the AP, installs
  + starts services (only git need be preinstalled). Auto-seeds .env from the
  example on first run.
- README + .env.example updated for the Kodi architecture

Dual HDMI is handled in hardware (external splitter off one port); no software
mirror -- see git history for why that can't work smoothly on this Pi.

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

90 lines
3.1 KiB
Python

import logging
import os
from datetime import timedelta
log = logging.getLogger(__name__)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
INSTANCE_DIR = os.path.join(BASE_DIR, "instance")
SECRET_KEY_PATH = os.path.join(INSTANCE_DIR, "secret_key")
DOTENV_PATH = os.path.join(BASE_DIR, ".env")
def load_dotenv(path=DOTENV_PATH):
"""Minimal .env loader (no dependency). Populates os.environ for any key
not already set, so real environment variables always win over the file.
Works for both `uv run run.py` and the systemd-launched service."""
if not os.path.exists(path):
return
with open(path) as f:
for line in f:
line = line.strip()
if not line or line.startswith("#") or "=" not in line:
continue
key, _, val = line.partition("=")
key = key.strip()
val = val.strip().strip('"').strip("'")
if key:
os.environ.setdefault(key, val)
def require(name):
val = os.environ.get(name)
if not val:
raise RuntimeError(
f"{name} is not set -- copy .env.example to .env and fill it in"
)
return val
def load_secret_key():
"""Read the persisted session-signing key, generating it if missing.
On a read-only overlay filesystem the write will fail; in that case we
fall back to an ephemeral in-memory key so the app still starts (sessions
just won't survive a restart). install.sh generates this file while the
card is writable, so in normal operation the write path isn't hit."""
if os.path.exists(SECRET_KEY_PATH):
with open(SECRET_KEY_PATH) as f:
return f.read().strip()
key = os.urandom(32).hex()
try:
os.makedirs(INSTANCE_DIR, exist_ok=True)
with open(SECRET_KEY_PATH, "w") as f:
f.write(key)
except OSError:
log.warning(
"could not persist secret key (read-only filesystem?) -- using an "
"ephemeral key; logins won't survive an app restart"
)
return key
load_dotenv()
class Config:
SECRET_KEY = load_secret_key()
PERMANENT_SESSION_LIFETIME = timedelta(days=3650)
SESSION_COOKIE_SAMESITE = "Lax"
USERNAME = require("MEDIAPI_USERNAME")
PASSWORD = require("MEDIAPI_PASSWORD")
MEDIA_ROOTS = [
p for p in os.environ.get("MEDIAPI_MEDIA_ROOTS", "/localmedia").split(":") if p
]
# Kodi does the playback; we drive it over its JSON-RPC HTTP endpoint.
# Kodi's web server must be enabled (install.sh configures this) and should
# be on a different port than this app. Defaults match install.sh.
KODI_HOST = os.environ.get("MEDIAPI_KODI_HOST", "127.0.0.1")
KODI_PORT = int(os.environ.get("MEDIAPI_KODI_PORT", "8090"))
KODI_USER = os.environ.get("MEDIAPI_KODI_USER", "kodi")
KODI_PASSWORD = os.environ.get("MEDIAPI_KODI_PASSWORD", "kodi")
KODI_URL = f"http://{KODI_HOST}:{KODI_PORT}/jsonrpc"
PORT = int(os.environ.get("MEDIAPI_PORT", "8080"))
VIDEO_EXTENSIONS = {
".mp4", ".mkv", ".avi", ".mov", ".m4v", ".webm", ".mpg", ".mpeg", ".ts", ".flv", ".wmv",
}