diff --git a/apps/master-api/src/f1tv/client.ts b/apps/master-api/src/f1tv/client.ts index c8ff4ab..4b87630 100644 --- a/apps/master-api/src/f1tv/client.ts +++ b/apps/master-api/src/f1tv/client.ts @@ -4,6 +4,32 @@ const F1TV_ORIGIN = "https://f1tv.formula1.com"; const DEFAULT_UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36"; +/** + * F1TV metadata levert `pictureUrl` als relatief pad + * (`1000010377-…` of `…/landscape_web`), niet als http(s)-URL. + * Covers/hero's gaan via image-resizer. + */ +export function resolveF1tvImageUrl( + raw: string | null | undefined, + opts?: { width?: number; height?: number; variant?: string } +): string | null { + const t = String(raw ?? "").trim(); + if (!t) return null; + if (/^https?:\/\//i.test(t)) return t; + const variant = opts?.variant || "landscape_web"; + const w = opts?.width ?? 768; + const h = opts?.height ?? 427; + let path = t.replace(/^\/+/, ""); + if (path.startsWith("image-resizer/")) { + return `${F1TV_ORIGIN}/${path}${path.includes("?") ? "" : `?w=${w}&h=${h}`}`; + } + if (path.startsWith("image/")) path = path.slice("image/".length); + // Already has variant suffix → keep; otherwise append landscape_web. + const hasVariant = /\/(landscape_web|portrait_web|hero_web|square_web)\b/i.test(path); + const withVariant = hasVariant ? path : `${path}/${variant}`; + return `${F1TV_ORIGIN}/image-resizer/image/${withVariant}?w=${w}&h=${h}`; +} + export type F1tvRequestAuth = { ascendontoken?: string | null; entitlementtoken?: string | null; diff --git a/apps/master-api/src/f1tv/events.ts b/apps/master-api/src/f1tv/events.ts index e819451..29a97fe 100644 --- a/apps/master-api/src/f1tv/events.ts +++ b/apps/master-api/src/f1tv/events.ts @@ -1,4 +1,5 @@ import type { ScheduleEventPublic } from "../viewer/schedule-events"; +import { resolveF1tvImageUrl } from "./client"; import { loadF1tvConfigOnly } from "./settings"; import type { F1tvMeetingSnapshot, F1tvSessionSnapshot } from "./tweaks"; @@ -29,6 +30,7 @@ export function sessionToPublic( const stop = new Date(session.endMs).toISOString(); const phase = phaseFor(session.startMs, session.endMs, session.state, nowMs); const playable = session.kind === "live_event" && phase === "live"; + const image = resolveF1tvImageUrl(session.imageUrl); return { id: toEventId(session.contentId), @@ -50,11 +52,11 @@ export function sessionToPublic( stopLocal: null, sportBucket: "racing", sportLabel: "Racing", - imageLandscape: session.imageUrl, - imageBackdrop: session.imageUrl, + imageLandscape: image, + imageBackdrop: image, imagePortrait: null, logoUrl: null, - images: session.imageUrl ? [session.imageUrl] : [], + images: image ? [image] : [], audioLanguages: [], }; } diff --git a/apps/master-api/src/f1tv/sync.ts b/apps/master-api/src/f1tv/sync.ts index ccf05f4..d56be34 100644 --- a/apps/master-api/src/f1tv/sync.ts +++ b/apps/master-api/src/f1tv/sync.ts @@ -1,7 +1,7 @@ import { AppError } from "../security/errors"; import type { F1tvContentOwners } from "./accounts"; import { decryptAccount } from "./accounts"; -import { ensureSessionId, fetchPage } from "./client"; +import { ensureSessionId, fetchPage, resolveF1tvImageUrl } from "./client"; import { loadF1tvSettings, saveF1tvSyncResult, @@ -136,9 +136,10 @@ function extractSessions( state: String(emf.state ?? meta.state ?? "").trim() || null, startMs, endMs: Number.isFinite(endMs) && endMs > 0 ? endMs : startMs + 2 * 3600_000, - imageUrl: + imageUrl: resolveF1tvImageUrl( String(meta.pictureUrl ?? meta.imageUrl ?? meta.embargoImage ?? "").trim() || - null, + null + ), defaultChannelId, accountIds: [accountId], });