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>
58 lines
2 KiB
Python
58 lines
2 KiB
Python
#!/usr/bin/env python3
|
|
"""Enable Kodi's JSON-RPC web server by seeding its guisettings.xml.
|
|
|
|
mediapi controls Kodi over HTTP JSON-RPC, which requires "Allow remote control
|
|
via HTTP" -- a setting normally toggled in Kodi's GUI. This seeds it headlessly.
|
|
|
|
IMPORTANT: Kodi rewrites guisettings.xml when it exits, so this must run while
|
|
Kodi is STOPPED (install.sh handles the stop/seed/start ordering). It merges
|
|
into any existing file, so re-running is safe.
|
|
|
|
Reads the desired values from the environment (with sensible defaults matching
|
|
.env.example):
|
|
MEDIAPI_KODI_PORT, MEDIAPI_KODI_USER, MEDIAPI_KODI_PASSWORD
|
|
|
|
Usage: configure-kodi.py [path/to/guisettings.xml]
|
|
(default: ~/.kodi/userdata/guisettings.xml)
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import xml.etree.ElementTree as ET
|
|
|
|
DEFAULT_PATH = os.path.expanduser("~/.kodi/userdata/guisettings.xml")
|
|
|
|
WANTED = {
|
|
"services.webserver": os.environ.get("MEDIAPI_KODI_WEBSERVER", "true"),
|
|
"services.webserverport": os.environ.get("MEDIAPI_KODI_PORT", "8090"),
|
|
"services.webserverusername": os.environ.get("MEDIAPI_KODI_USER", "kodi"),
|
|
"services.webserverpassword": os.environ.get("MEDIAPI_KODI_PASSWORD", "kodi"),
|
|
"services.webserverauthentication": "true",
|
|
"services.webserverssl": "false",
|
|
}
|
|
|
|
|
|
def main():
|
|
path = sys.argv[1] if len(sys.argv) > 1 else DEFAULT_PATH
|
|
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
|
|
if os.path.exists(path):
|
|
tree = ET.parse(path)
|
|
root = tree.getroot()
|
|
else:
|
|
root = ET.Element("settings", {"version": "2"})
|
|
tree = ET.ElementTree(root)
|
|
|
|
existing = {el.get("id"): el for el in root.findall("setting")}
|
|
for setting_id, value in WANTED.items():
|
|
el = existing.get(setting_id)
|
|
if el is None:
|
|
el = ET.SubElement(root, "setting", {"id": setting_id})
|
|
el.text = value
|
|
|
|
tree.write(path, encoding="utf-8", xml_declaration=True)
|
|
print(f"configure-kodi: web server enabled on port {WANTED['services.webserverport']} ({path})")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|