From 392099770a0678a107ccc82e4d7c96d6b64491a7 Mon Sep 17 00:00:00 2001 From: npeter83 Date: Wed, 7 Oct 2026 23:16:38 +0200 Subject: [PATCH] fix(plex): an episode Plex has no number for is named by its title alone The OS media title printed the missing number as "Enull", the player and info headers as a bare "E". One helper for the three lines. --- frontend/src/components/PlexInfo.tsx | 3 ++- frontend/src/components/PlexPlayer.tsx | 10 +++------- .../src/lib/plexDetail.episodeLine.test.ts | 20 +++++++++++++++++++ frontend/src/lib/plexDetail.ts | 11 ++++++++++ 4 files changed, 36 insertions(+), 8 deletions(-) create mode 100644 frontend/src/lib/plexDetail.episodeLine.test.ts diff --git a/frontend/src/components/PlexInfo.tsx b/frontend/src/components/PlexInfo.tsx index 9bca97cb..79dcce46 100644 --- a/frontend/src/components/PlexInfo.tsx +++ b/frontend/src/components/PlexInfo.tsx @@ -10,6 +10,7 @@ import { DetailCustomizeMenu, Filterable, PrefToggle } from "./plexDetailUi"; import { DetailAside, DetailAsideToggles, DetailKeywords } from "./plexDetailAside"; import { DETAIL_POSTER, + episodeLine, tmdbBackdropUrl, useCrossfadeBackdrop, useDetailAside, @@ -315,7 +316,7 @@ export default function PlexInfo({ )} {detail.kind === "episode" && (

- S{detail.season_number}·E{detail.episode_number} — {detail.title} + {episodeLine(detail)}

)} diff --git a/frontend/src/components/PlexPlayer.tsx b/frontend/src/components/PlexPlayer.tsx index c14ec731..575332ea 100644 --- a/frontend/src/components/PlexPlayer.tsx +++ b/frontend/src/components/PlexPlayer.tsx @@ -52,6 +52,7 @@ import { Ctrl, PanelHead } from "./plexPlayerControls"; import { PlexPlayerSettings } from "./PlexPlayerSettings"; import { PlexStatsOverlay } from "./PlexStatsOverlay"; import { createFragMeter } from "../lib/plexStats"; +import { episodeLine } from "../lib/plexDetail"; import { HlsEngine } from "../lib/hlsEngine"; import { createHlsPort } from "../lib/hlsPort"; import { enabled, log } from "../lib/log"; @@ -586,10 +587,7 @@ export default function PlexPlayer({ itemId, onClose, queue, fromStart = false } useMediaSession( videoEl, detail && { - title: - detail.kind === "episode" && detail.season_number != null - ? `S${detail.season_number}·E${detail.episode_number} — ${detail.title}` - : detail.title, + title: detail.kind === "episode" ? episodeLine(detail) : detail.title, artist: detail.kind === "episode" ? detail.show_title : detail.year ? String(detail.year) : null, artwork: api.plexImageUrl(id), @@ -1258,9 +1256,7 @@ export default function PlexPlayer({ itemId, onClose, queue, fromStart = false } {detail?.kind === "episode" && detail.show_title ? detail.show_title : detail?.title} {detail?.kind === "episode" && ( -
- S{detail.season_number}·E{detail.episode_number} — {detail.title} -
+
{episodeLine(detail)}
)} {/* Live wall clock — handy on a lean-back / HTPC screen (toggle + style in the Clock tab). */} diff --git a/frontend/src/lib/plexDetail.episodeLine.test.ts b/frontend/src/lib/plexDetail.episodeLine.test.ts new file mode 100644 index 00000000..a5454062 --- /dev/null +++ b/frontend/src/lib/plexDetail.episodeLine.test.ts @@ -0,0 +1,20 @@ +// @vitest-environment jsdom +import { describe, expect, it } from "vitest"; +import { episodeLine } from "./plexDetail"; + +describe("episodeLine", () => { + it("names the episode by its season and number", () => { + expect(episodeLine({ season_number: 2, episode_number: 7, title: "Pilot" })).toBe( + "S2·E7 — Pilot", + ); + }); + + it("is the title alone for an episode Plex has no number for", () => { + expect(episodeLine({ season_number: 1, episode_number: null, title: "Special" })).toBe( + "Special", + ); + expect(episodeLine({ season_number: null, episode_number: 3, title: "Special" })).toBe( + "Special", + ); + }); +}); diff --git a/frontend/src/lib/plexDetail.ts b/frontend/src/lib/plexDetail.ts index c2c54de5..002e068a 100644 --- a/frontend/src/lib/plexDetail.ts +++ b/frontend/src/lib/plexDetail.ts @@ -210,6 +210,17 @@ export function epLabel(ep?: PlexCard | null): string | undefined { return ep.title; } +/** An episode's line under its show's name: "S1·E4 — Title" — or the title alone when Plex has no + * number for it (a special): a template literal printed the missing one as "Enull", JSX as "E". */ +export function episodeLine(ep: { + season_number?: number | null; + episode_number?: number | null; + title: string; +}): string { + if (ep.season_number == null || ep.episode_number == null) return ep.title; + return `S${ep.season_number}·E${ep.episode_number} — ${ep.title}`; +} + // Plex names seasons in the library's language ("Season 1", "Specials"), so a Hungarian reader got // English there. A generic name is rebuilt from the number in the reader's language; a season // with a name of its own ("Book One: Water") keeps it.