import type { Item } from './types'; import { medienURL } from './api'; /** Entschärft Text, der in HTML landet. */ export function esc(wert: unknown): string { return String(wert ?? '').replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[c]!); } /** Laufzeit als „2 Std 16 Min" oder „46 Min". */ export function laufzeit(item: Item): string { if (!item.RunTimeTicks) return ''; const min = Math.round(item.RunTimeTicks / 600_000_000); return min >= 60 ? `${Math.floor(min / 60)} Std ${min % 60} Min` : `${min} Min`; } /** Zeit als Uhrwerk: „1:02:03" oder „7:15". */ export function uhr(sekunden: number): string { if (!isFinite(sekunden)) return '–:--'; const s = Math.max(0, Math.floor(sekunden)); const h = Math.floor(s / 3600); const m = Math.floor((s % 3600) / 60); const r = s % 60; return (h ? `${h}:${String(m).padStart(2, '0')}` : `${m}`) + ':' + String(r).padStart(2, '0'); } /** Der Folgentitel: „S2:E4 · Der Fall". */ export function folgentitel(item: Item): string { const nummer = item.ParentIndexNumber != null && item.IndexNumber != null ? `S${item.ParentIndexNumber}:E${item.IndexNumber}` : ''; return [nummer, item.Name].filter(Boolean).join(' · '); } /** Ob ein Eintrag ein Bild dieser Art hat. */ export function hatBild(item: Item, art: 'Primary' | 'Backdrop'): boolean { return art === 'Backdrop' ? Boolean(item.BackdropImageTags?.length || item.ParentBackdropImageTags?.length) : Boolean(item.ImageTags?.[art]); } /** Das Bild eines Eintrags — mit den Ersatzwegen, die Jellyfin nötig macht. * * Eine Folge hat selten ein eigenes Hintergrundbild, aber ihre Serie hat * eins. Ohne diese Kette bliebe die halbe Startseite grau. */ export function bild(item: Item, art: 'Primary' | 'Backdrop', breite = 400): string { if (art === 'Backdrop') { if (item.BackdropImageTags?.length) { return medienURL(`/Items/${item.Id}/Images/Backdrop`, { tag: item.BackdropImageTags[0]!, maxWidth: breite, quality: 90, }); } if (item.ParentBackdropItemId) { return medienURL(`/Items/${item.ParentBackdropItemId}/Images/Backdrop`, { tag: item.ParentBackdropImageTags?.[0] ?? '', maxWidth: breite, quality: 90, }); } } if (item.ImageTags?.Primary) { return medienURL(`/Items/${item.Id}/Images/Primary`, { tag: item.ImageTags.Primary, maxWidth: breite, quality: 90, }); } if (item.SeriesPrimaryImageTag && item.SeriesId) { return medienURL(`/Items/${item.SeriesId}/Images/Primary`, { tag: item.SeriesPrimaryImageTag, maxWidth: breite, quality: 90, }); } return ''; } /** Die Felder, die die Startseite braucht. */ export const FELDER = 'Overview,Genres,MediaSources,ParentBackdropItemId,ParentBackdropImageTags';