diff --git a/docs/api-conventions.md b/docs/api-conventions.md index 85ae19d5c..c2eb4e665 100644 --- a/docs/api-conventions.md +++ b/docs/api-conventions.md @@ -289,6 +289,10 @@ returns `null` when the channel has no logo so the SPA falls back to its generat raw un-rooted `{path, contentType}` form is still used only by the channel **editor** DTO (`ChannelDetailResponseModel.Logo`), which round-trips it back on save. +`GET /api/v1/watermarks` returns picker-grade rows that carry `imageSource` alongside `id`/`name` +(#67), so a client can find the seeded logo-driven `Channel Bug` preset without matching its +user-editable name. The full geometry still requires `GET /api/v1/watermarks/{id}`. + ### 4a. Artwork content type is sniffed, never client-supplied (issue #283) The uploaded-artwork surfaces (channel logo, watermark) must never trust a client-declared content diff --git a/docs/channels.md b/docs/channels.md index 0207c7d59..a8b9610bf 100644 --- a/docs/channels.md +++ b/docs/channels.md @@ -139,6 +139,32 @@ The scheduling engine (`ErsatzTV.Core/Scheduling/`) resolves schedule items into ## Watermarks -`ChannelWatermark` supports modes: Permanent, Intermittent, OpacityExpression. Image sources: custom upload, channel logo, or built-in resource. Positioned with percentage-based margins and z-index. +`ChannelWatermark` supports modes: Permanent, Intermittent, OpacityExpression. Image sources: custom +upload, channel logo, or built-in resource. Positioned with percentage-based margins and z-index. -Note: `ChannelLogoGenerator.GenerateChannelLogoUrl()` hardcodes `localhost` for watermark logo fetching — see issue #1 for details. +A watermark is a **shared, named entity** (unique `Name`), referenced by playout items, schedule +items, block items and decos; a channel points at one via `Channel.WatermarkId`. It is not +per-channel state. + +`DbInitializer` seeds one shared preset named **`Channel Bug`** (`ImageSource = ChannelLogo`, +Permanent, TopLeft, Scaled 5% width, 1%/1% margins, 80% opacity). Because `ChannelLogo` resolves +each channel's own `ArtworkKind.Logo` artwork at render time, this single row makes every channel +that points at it use its own logo as its on-screen bug — one uploaded image drives both the guide +listing and the bug (#67). An existing `Channel Bug` row is adopted untouched, never overwritten, and +a `ConfigElement` marker (`watermark.channel_bug_seeded`) makes the seed run once per database, so a +deliberately deleted preset is not resurrected on the next restart. + +Quick-add channel creation defaults to the preset, and the channel editor's Branding tab exposes it +as a "Use logo as on-screen bug" toggle. On a **fresh** install the seed also stamps the preset onto +the system channel templates it creates, so the library-to-lineup builder (which inherits +`WatermarkId` from the selected template) gets the default too. On an existing install the templates +are left alone, so builder-created and auto-tuned channels there inherit whatever the template +already specifies. + +**Limitation:** a logo set via **External logo URL** cannot drive the bug. `WatermarkSelector` +resolves it to the URL and then `File.Exists`-checks it, which is never true, so the watermark is +silently dropped — the URL wins for the guide listing but disables the on-screen bug. Tracked as +**#502**; the editor does not offer a bug preview in that case. + +Note: `ChannelLogoGenerator.GenerateChannelLogoUrl()` hardcodes `localhost` for watermark logo +fetching — see issue #1 for details. diff --git a/docs/decisions.md b/docs/decisions.md index a6208807f..120311eaa 100644 --- a/docs/decisions.md +++ b/docs/decisions.md @@ -2361,3 +2361,43 @@ fixed here: sweep. This is *why* it cannot cross-delete in a mixed library, but it also means a music video removed from Jellyfin is never removed from ErsatzTV. Pre-existing and orthogonal to this change; it belongs with that scanner's other gaps (no `ItemId`/`Etag`, path-keyed identity — see #488). + +## 2026-07-20 — One logo drives the bug via a shared ChannelLogo preset, not new schema (#67) + +#67 asked that one uploaded image drive both the listing logo and the on-screen bug, separably +overridable, with preview. Most of it already existed: `ChannelWatermarkImageSource.ChannelLogo` +resolves the channel's own logo artwork at render time at all three watermark precedence levels, and +`Custom` already provides the independent override. Production had already been running exactly this +pattern by hand — 43 channels pointing at one hand-made `Channel Bug` preset. + +**Decision: seed that preset rather than add per-channel bug columns.** A watermark is a shared named +entity, so per-channel geometry would need either a dual-provider migration or one watermark row per +channel (under a unique-name index). Since `ChannelLogo` resolves per channel at render time, a single +shared row already delivers the user-visible behavior with no schema change. + +- The seed **adopts** an existing `Channel Bug` row untouched, so an operator's tuned geometry is never + overwritten, and a `ConfigElement` marker (`watermark.channel_bug_seeded`) makes it run once per + database rather than once per name-absence — `ChannelWatermark` has no `IsSystem` flag and + `DbInitializer.Initialize` runs at every startup, so a name-only guard would resurrect a deliberately + deleted preset forever. Covered by `DbInitializerChannelBugWatermarkTests`. +- `WatermarkResponseModel` gained `imageSource` (additive under the frozen `/api/v1`, #286) so clients + identify logo-driven presets generically instead of matching a user-editable name. Note the limit: + once a *second* logo-driven preset exists, `imageSource` identifies the class but not *the* default, + so `findLogoBugWatermark` prefers the seeded name as a deterministic tiebreak. +- The default is applied by the SPA's quick-add **creation** path, not by inferring "newness" in the + editor — quick-add creates through the API and then navigates to the editor, so the editor only ever + loads an existing row. The editor toggle reflects the **referenced** watermark's `imageSource`; an + earlier draft searched the list instead, which (because `getWatermarks()` sorts by name) would have + silently repointed channels bound to a non-first logo-driven preset. Caught in independent review + and pinned by a regression test. +- The builder flow is covered on **fresh installs only**, by stamping the preset onto the system + channel templates the seed itself creates; existing installs' templates are never mutated. +- Server-side create defaulting was rejected: making an omitted `watermarkId` mean "give me a + watermark" would surprise machine clients of the frozen API. `POST /api/v1/channels/auto-tune` is + therefore unchanged. +- **Not fixed here:** external-URL logos never render a bug (`WatermarkSelector` `File.Exists`-checks a + URL). Pre-existing, lands in the FFmpeg render path, tracked as **#502**. This change only stops the + preview from promising it. + +**Accepted trade-off:** every channel on the shared preset shares one geometry; per-channel tweaks mean +creating a second preset on the Watermarks screen.