diff --git a/CommunityDiscordMedia.md b/CommunityDiscordMedia.md index 7f7bc2b..29302ca 100644 --- a/CommunityDiscordMedia.md +++ b/CommunityDiscordMedia.md @@ -287,7 +287,7 @@ Discord roles); the publish path must start with the user's own name. - [ ] dock + README; remove streamers from configure.py; delete `streamers.json` - [ ] test: OBS with an app password; Go Live with no key; a non-streamer gets denied -### Phase 12 — frame-accurate timecode for multicam via an overscan band (built; deploy pending) +### Phase 12 — frame-accurate timecode for multicam via an overscan band ✅ live, verified (OBS: strip, −3 ms drift, 0 gaps) Target: clips from several streamers align to the frame (16.7 ms @60) on a multicam timeline; the game image is untouched and finals carry nothing. Reference clock = the server; streamer PC clocks are never trusted. diff --git a/README.md b/README.md index 4ac726f..27bed1d 100644 --- a/README.md +++ b/README.md @@ -289,9 +289,10 @@ MediaMTX asks dumpd (`POST /mtx-auth` on 127.0.0.1:8081, reachable only from Doc connection. Reads/playback are allowed (viewers are gated by Caddy + Authentik). Publishing needs a token minted by `GET /me/stream-token` behind Authentik for a member of `streamers`/`admins`/`media-admins`; dumpd checks the HMAC signature (`/etc/vanguard/stream-token.secret`, generated into `.env` as `STREAM_TOKEN_SECRET`), -the expiry, and that the path starts with that user's name. MediaMTX hot-reloads paths/recording settings -but **not** the auth section: after changing auth, `docker compose restart mediamtx` between streams. -`configure.py` reloads Caddy in place and restarts only copyparty. +the expiry, and that the path starts with that user's name. MediaMTX does not pick up `mediamtx.yml` changes +live on this setup: after any change to it, `docker compose restart mediamtx` between streams. Day-to-day +operations (streamers, tokens, titles) never touch that file. `configure.py` reloads Caddy in place and +restarts only copyparty. ## Timecode for multicam editing (frame accurate) diff --git a/STATUS.md b/STATUS.md index b906490..9e8a081 100644 --- a/STATUS.md +++ b/STATUS.md @@ -25,7 +25,7 @@ Source of truth: this repo, `techy/Media-Server-Setup` on the stack's own Gitea | Who | Activity | Where | Login | |---|---|---|---| -| Streamer | One-time: dock → Timecode band → "Add band + clock source" (OBS) or leave the Go Live checkbox on — gives editors frame-accurate timecode | OBS dock / Go Live | — **pending deploy** | +| Streamer | One-time: dock → Timecode band → "Add band + clock source" (OBS) or leave the Go Live checkbox on — gives editors frame-accurate timecode | OBS dock / Go Live | live | | Streamer | Type a title (live preview + naming rules), Go Live, Stop. "Dump tonight" if it was junk (3-day undo). Manual-setup accordion with endpoints and a hidden token | OBS browser dock `https://ovh1…/dock/` | Discord login in the dock; dock mints a 12 h token per Go Live — **no passwords, verified live** | | Gamer | Nothing; clips save where they always did | their clipper + Syncthing, paired once | — | | Editor | Multicam: drop clips on a timeline, sync by the embedded timecode track (frame-accurate when the band was on), cross-check with voice audio; export at the game resolution to drop the band | Resolve / Premiere | — | @@ -47,8 +47,8 @@ Source of truth: this repo, `techy/Media-Server-Setup` on the stack's own Gitea |---|---|---| | MediaMTX | RTMP/SRT ingest, records fMP4; publish auth delegated to dumpd (tokens via `/mtx-auth`) | live, token auth verified | | copyparty | browse/scrub/download/finals upload | behind Authentik, group permissions (`editors`, `media-admins`), Discord-only, no WebDAV | live | -| dumpd + dock | dump, stream tokens, MediaMTX auth hook, `/time`; OBS dock with timecode-band setup | identity from Authentik headers | live; band/timecode **pending deploy** | -| Timecode | `bin/vgstrip.py` decoder, `tmcd` + sidecar in the sweep, `/dock/clock/` strip page, Go Live band composite, portal crop | server clock is the reference | **built, pending deploy** | +| dumpd + dock | dump, stream tokens, MediaMTX auth hook, `/time`; OBS dock with timecode-band setup | identity from Authentik headers | live | +| Timecode | `bin/vgstrip.py` decoder, `tmcd` + sidecar in the sweep, `/dock/clock/` strip page, Go Live band composite, portal crop | server clock is the reference | **live, verified** (OBS: frame-accurate, 0 gaps) | | Syncthing | clip intake, UI at `/sync/` | UI behind Authentik + `media-admins` | live | | normalize sweep | editing standard, one copy per clip | live | | nftables | host firewall (22, 80/443, 1935, 2222, 3306, 8890/udp, 22000) | live | @@ -65,7 +65,7 @@ Source of truth: this repo, `techy/Media-Server-Setup` on the stack's own Gitea ## Unfinished, in rough priority -0. **Deploy phase 12** (timecode): vgstrip + sweep + dumpd `/time` + configure (Caddy `/time` routes, MediaMTX `%f`) + dock/clock + portal. Then a streamer adds the band and we check a recording's `tmcd` and sidecar. +0. `docker compose restart mediamtx` at a quiet moment so recording filenames gain microseconds (the arrival-time fallback); everything else in phase 12 is live. 1. Authentik: `discord-roles-to-groups` policy so `streamers`/`editors`/`media-admins`/`bot-users` follow Discord roles (`bot-users` group needed for the bot host). Set the provider's Invalidation flow to `default-provider-invalidation-flow` so every logout forces a full re-login. 2. Confirm plugin 0.1.3 under Jellyfin → Plugins and the Gitea runner registered. 3. Portal viewer during a real stream: corner chip `webrtc`; Windows Go Live with WarDogs audio (first Windows streamer). @@ -86,7 +86,8 @@ Source of truth: this repo, `techy/Media-Server-Setup` on the stack's own Gitea - Streams are not re-encoded for VFR/gaps by default (dropped frames are legitimate; a 3 h re-encode is hours of CPU); players hold the last frame across a gap. The timecode sidecar lists gaps. **Gap filling is available** (`FILL_GAPS_MS` in `normalize-sweep.service`, README "Timecode") — turn on when the box has a hardware encoder. Clips always get CFR. - The sweep's VFR check used a gawk-only function; Debian's mawk made it pass everything silently. Portable now. - A deploy block that is only half-run leaves old binaries in `/usr/local/bin`; `grep -c` the installed script before blaming logic. -- MediaMTX hot-reloads `paths`/recording settings but NOT `authMethod`/auth settings: those need a container restart (between streams). +- MediaMTX does not reload `mediamtx.yml` live here (bind-mounted file): any change to it needs `docker compose restart mediamtx` between streams. Nothing day-to-day touches that file. +- **Timecode verified live (2026-10-31)**: OBS + NVENC with the band → `source: strip`, drift −3 ms over 80 s, 0 gaps, 1920×1092 (band present); arrival time was 2.3 s late vs the first frame, which the strip corrected. Go Live from Linux Chromium (software encode) → strip decoded fine but 239 gaps and −85 ms: frame dropping, as expected on that path. - MediaMTX stamps recording filenames in its container clock: without `TZ` it is UTC and the plugin reads names as Eastern (4 h off). `TZ=America/New_York` is set on the container. - MediaMTX 1.21 refuses to start without `webrtcAdditionalHosts` or `webrtcIPsFromInterfaces`; configure.py writes the public host in. - Jellyfin honours provider-supplied season numbers on a Shows library (day seasons appeared). Next check: Monday 04:10 ET should fold them into a week season. diff --git a/configure.py b/configure.py index daad6ac..9c1bd6f 100755 --- a/configure.py +++ b/configure.py @@ -364,7 +364,8 @@ srtAddress: :8890 # ---- auth ------------------------------------------------------------- # Publishing is authorised by dumpd (/mtx-auth) with short-lived tokens minted for Discord-authenticated # members of the streamers group. Reads are allowed; viewers are gated by Caddy + Authentik. No passwords. -# NOTE: MediaMTX hot-reloads paths/recording settings but NOT the auth section — restart after auth changes. +# NOTE: on this setup (bind-mounted file) MediaMTX does not reload this file live: restart after any change here. +# Day-to-day operations (streamers, tokens, titles) never touch this file. authMethod: http authHTTPAddress: http://{auth_gw}:8081/mtx-auth authHTTPExclude: @@ -475,8 +476,9 @@ def apply(domain, admin_pw, streamers, editors, auth_host="", watch_host="", dis run(dc + ["up", "-d", "--build"]) # recreates only services whose compose definition changed run(dc + ["restart", "copyparty"]) # copyparty re-reads its config on start run(dc + ["exec", "caddy", "caddy", "reload", "--config", "/etc/caddy/Caddyfile"]) # zero-downtime - print(" mediamtx: hot-reloads most of mediamtx.yml, but NOT auth changes. If you changed auth settings, run") - print(" docker compose restart mediamtx between streams (cuts a live recording).") + print(" mediamtx: config changes are NOT picked up live on this setup (bind-mounted file). If mediamtx.yml changed,") + print(" run docker compose restart mediamtx between streams (cuts a live recording). Adding/removing") + print(" streamers or tokens never touches mediamtx.yml, so day-to-day nothing restarts.") def show(domain, admin_pw, streamers, editors, auth_host="", watch_host="", git_host="", bot_host="", live_host=""): print(f"\n== domain : {domain or '(not set)'}")