Aggiunge copertina sponsor custom solo Premium Full, con override in wizard e slate sui nodi di streaming.

Permette upload da portale e app con ereditarietà partita→squadra→società, generazione MP4 via ffmpeg e distribuzione su home lab e nodi CPX per pause e assenza segnale.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-19 23:47:17 +02:00
co-authored by Cursor
parent a51d5e8da5
commit 41d4235258
62 changed files with 1487 additions and 20 deletions
+24
View File
@@ -16,6 +16,7 @@ SECRET = os.environ.get("STREAM_NODE_AGENT_SECRET", "")
LISTEN = os.environ.get("STREAM_NODE_AGENT_LISTEN", "0.0.0.0:9100")
HLS_BASE = os.environ.get("STREAM_NODE_LOCAL_HLS_URL", "http://127.0.0.1:8888").rstrip("/")
LOG_DIR = os.environ.get("STREAM_NODE_RELAY_LOG_DIR", "/var/log")
SLATES_DIR = os.environ.get("STREAM_NODE_SLATES_DIR", "/slates/custom")
_lock = threading.Lock()
# session_id -> {"pid": int, "path": str, "proc": Popen}
@@ -189,6 +190,29 @@ class Handler(BaseHTTPRequestHandler):
_stop_session(session_id)
self._json(200, {"stopped": True, "session_id": session_id})
def do_PUT(self) -> None:
if not _authorized(self):
self._json(401, {"error": "unauthorized"})
return
parsed = urlparse(self.path)
if not parsed.path.startswith("/slates/"):
self._json(404, {"error": "not found"})
return
name = parsed.path.split("/slates/", 1)[1].strip("/")
if not name or "/" in name or ".." in name:
self._json(400, {"error": "invalid slate name"})
return
length = int(self.headers.get("Content-Length") or 0)
if length <= 0:
self._json(400, {"error": "empty body"})
return
data = self.rfile.read(length)
os.makedirs(SLATES_DIR, exist_ok=True)
dest = os.path.join(SLATES_DIR, name)
with open(dest, "wb") as out:
out.write(data)
self._json(201, {"path": dest, "bytes": len(data), "name": name})
def main() -> None:
host, port_s = LISTEN.rsplit(":", 1)