Jellyfin merges several videos in one non-season folder into a single episode with versions, which collapsed a night of helmet cams into one entry. Recording still lands in <Title>/.live/. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Vanguard Metadata — Jellyfin plugin
Metadata provider for gameplay recordings. Each streamer/submitter folder is a show; season and episode numbers come from the recording timestamp under a rolling policy; names, avatars and episode titles come from Discord via Authentik's identity link; new recordings and clips are announced in a Discord channel.
Layout
Jellyfin.Plugin.Vanguard.Core/— pure logic:SeasonPolicy(rolling buckets, naming, overrides),ItemTimestamp(sidecar → filename patterns → mtime). No Jellyfin dependency. Unit-tested.Jellyfin.Plugin.Vanguard/— the plugin: providers (Series, Series images, Season, Episode), services (Discord REST, Authentik REST, event store, identity resolver, feed), scheduled tasks (Discord snapshot every 5 min; season roll nightly after the rollover hour), config page.Jellyfin.Plugin.Vanguard.Tests/— xunit.
Season scheme
| age of item | season # | season name |
|---|---|---|
| current ISO week | YYYYMMDD |
"Thursday, Oct 29" |
| current month | 1YYYYWW |
"Week 44" |
| current year | YYYYMM |
"October 2026" |
| older | YYYY |
"2026" |
Episode # = MMDDHHMMSS of the local timestamp — never changes when the season rolls.
Broadcast day rolls at the configured hour (default 04:00). Overrides: seasonNumber=Name, one per line.
Timestamp sources, in order
<name>.json/<name>.clip.jsonsidecar with"timestamp"(UTC ISO-8601) — written by the clip bot- filename: MediaMTX
2026-10-29_21-03-11, Game Bar2026-10-29 21-03-11, ShadowPlay2026.10.29 - 21.03.11, Steam20261029_210311 - file modification time (Syncthing preserves the gamer's mtime)
Build / package
./package.sh # tests + Release build → dist/VanguardMetadata_<ver>.zip + manifest.json
Requires .NET 10 SDK. Bump version in build.yaml for a release.
Install on the server (manual, v0.1)
scp dist/VanguardMetadata_0.1.0.0.zip debian@HOST:/tmp/
ssh debian@HOST 'sudo -u "#1000" mkdir -p "/srv/appdata/jellyfin/config/plugins/Vanguard Metadata_0.1.0.0" && sudo -u "#1000" python3 -m zipfile -e /tmp/VanguardMetadata_0.1.0.0.zip "/srv/appdata/jellyfin/config/plugins/Vanguard Metadata_0.1.0.0/" && cd ~/vanguard-stream && docker compose restart jellyfin'
Later: serve dist/ at https://HOST/plugins/ (Caddy) and add it as a repository in Jellyfin so updates
come through the Catalog.
Runs with an empty config
Seasons, episode numbers, timestamps and the nightly roll need nothing external. Without a Discord bot token the show is named after its folder, has no poster, and episode titles are the dock title; without Authentik no folder→Discord mapping is attempted; without a feed channel nothing is posted. Add the credentials later and re-run "roll seasons" / refresh the libraries to pick up names and avatars.
Configure (Dashboard → Plugins → Vanguard Metadata)
- Discord: bot token (add a Bot to the existing Authentik Discord application; enable the Server Members intent; invite it to the server), guild ID, feed channel ID, public Jellyfin URL.
- Authentik: URL + API token of a read-only service account (used to map folder → Discord user).
- Folder → identity overrides if a streamer's folder name isn't their Authentik username.
- Libraries (Dashboard → Libraries → each of streams/clips): content type Shows; metadata downloaders: only "Vanguard Metadata" for Series, Season and Episode; image fetchers: Vanguard Metadata (Series) and Screen Grabber (Episode) — the grab is what the Discord embed attaches.
Tasks (Dashboard → Scheduled Tasks → Vanguard)
- "snapshot Discord events": every 5 min + startup. Run it once after configuring.
- "roll seasons": daily at rollover+10 min. Run it once after the first library scan.