You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
-`tmdb_api_key` — optional; only `fetch_images.py` uses it. Free key at https://www.themoviedb.org/settings/api
139
+
-`tmdb_api_key` — optional; used by `fetch_images.py` for verified TMDB logo lookups.
140
+
Without it, every channel gets a generated badge instead (icons still work). Free key at
141
+
https://www.themoviedb.org/settings/api
140
142
-`auth_username` / `auth_password` — optional HTTP Basic Auth. **Both blank = auth disabled.** When set, every backend request requires them.
141
143
-`recipes_enabled` (bool, default `false`), `recipe_interval_hours` (number, default `12`) — live-channel scheduler (see Live Channels).
142
144
-`tunarr_channel_group` (string, optional) — Tunarr `groupTitle` for all created channels (default `"tunarr"`).
@@ -159,7 +161,20 @@ and the code — don't restate them here.
159
161
-**`generate_from_collections.py`** — one channel per Plex collection via `{"collection":"Name"}`. Manages the collection block (default ch 80+): keeps everything below `--base`, regenerates from `--base` up. Re-run any time Kometa changes collections.
160
162
-**`channel_engine.py`** — shared, **pure, importable** resolution engine (no `config.json`/argv/`sys.exit`), so it's safe to import into the long-lived FastAPI process. Holds the resolution helpers, franchise `match_titles` (word-boundary), and the in-place live-channel updaters (`read_channel_programming`, `update_channel_in_place`). Imported by `create.py` at runtime and in-process by `recipes_router.py` — **must stay in the Dockerfile `COPY` line**. `build_library_index` indexes **all** enabled movie and shows libraries (not just the first — a Plex server can expose several, e.g. `TV Shows` + `Cartoons`), and indexes a show that appears in more than one library **once**, preferring the copy with the most playable (non-`missing`) episodes so a dead duplicate can't shadow the real one or inflate the live-diff into churn.
161
163
-**`create.py`** — thin CLI wrapper around `channel_engine`. Reads `channels.json`, indexes the Tunarr library (case-insensitive exact title match), and deploys (delete-then-create; `--from N` scopes, `--protect N1,N2` preserves specific channels). Builds 30-day rolling random schedules (no dead air). The delete/recreate path is **initial-deploy only** — never for live channels.
162
-
-**`fetch_images.py`** — for solo-title channels, finds the best TMDB clearlogo and sets the Tunarr channel `icon.path` so Plex shows a real logo in the guide. Multi-title channels are skipped. Dry-run by default; `--apply` to commit. Requires `tmdb_api_key`.
164
+
-**`fetch_images.py`** — sets every channel's Tunarr icon. Verified TMDB logos for
165
+
solo-title/marathon/franchise/network/studio channels (the result's name must match the
166
+
query after normalization — never `results[0]`); generated badge art for every other kind
167
+
and any TMDB miss. Badges upload via Tunarr `POST /api/upload/image`. Channels pinned from
168
+
the Channels editor (`"icon": {"pinned": true}` in channels.json) are skipped; the script
169
+
never writes channels.json. `tmdb_api_key` is optional — without it everything badges.
Tunarr upload/icon helpers (no `config.json`/argv/`sys.exit`). Imported by `fetch_images.py`
173
+
and in-process by `channels_router.py`. **Must stay in the Dockerfile `COPY` line.**
174
+
-**`badge_renderer.py`** — shared, **pure** Pillow badge rendering from committed
175
+
`badge_assets/` (Tabler glyphs MIT, Anton font OFL; regenerate via
176
+
`scripts/make_badge_assets.py`). Badges carry the channel name because Plex hides text
177
+
labels once an icon is set. **Module + `badge_assets/` must stay in the Dockerfile `COPY` lines.**
163
178
-**`sync_plex.py`** — reconciles Tunarr's XMLTV channel list into Plex's DVR mapping (read-then-update; **never deletes** the DVR). Falls back to printing the XMLTV URL + manual steps.
164
179
165
180
## Channel Numbering Scheme
@@ -277,6 +292,12 @@ the basis for future era-matched pooling (90s ads → 90s channel; see `docs/ide
277
292
**Mid-roll (ads inside a show) is deliberately not used — it doesn't stream on hardware-accelerated
278
293
(QSV) Tunarr; see [`docs/tunarr-commercials-findings.md`](docs/tunarr-commercials-findings.md).**
279
294
295
+
**Icon pin (optional).** A channel may carry `"icon": {"mode": "badge"|"tmdb"|"custom",
296
+
"url": "…", "pinned": true}` — written only by `POST /api/channels/{number}/icon` (the
297
+
Channels-editor icon control). `fetch_images.py` skips pinned channels and never writes
298
+
this field; removing it (the editor's "Reset to automatic") returns the channel to the
299
+
automatic art pass.
300
+
280
301
## API Endpoints
281
302
282
303
All endpoint tables — **Pipeline**, **Recipe**, **Tunarr**, **TMDB**, **Plex** — live in
@@ -303,10 +324,12 @@ AI" toggle is on; Collections only if opted in.
303
324
-**Saved on every change** via a debounced (~500ms) PUT in `PlannerStep` (guarded by
304
325
`restoredRef` so the first-mount restore never overwrites the file).
305
326
-**Restored on every Planner mount** (both Nuke and Edit modes) — `isEdit` gate removed.
306
-
-**Nuke does NOT clear picks** — `handleNuke` is intentionally a no-op for state.
307
-
-**"Clear all"** in the Planner build bar is the only thing that resets picks: clears
308
-
`selected`, `curate`, active genre/decade toggles and all batch toggles to defaults, then
309
-
calls `DELETE /pipeline/planner-state`.
327
+
-**Nuke clears candidate selections** — `handleNuke` resets `selected`, `curate`, and
328
+
`aiExtras`; genres/decades/comm/autoUpdate are preserved. The debounced save persists the
329
+
blank selections to `planner_state.json` automatically.
330
+
-**"Clear all"** in the Planner build bar does a full reset: clears `selected`, `curate`,
331
+
active genre/decade toggles and all batch toggles to defaults, then calls
0 commit comments