Fix F1TV cover URLs: resolve relative pictureUrl via image-resizer.

F1 metadata returns paths like 1000…/id, not full HTTPS URLs — hub cards and hero stayed blank without this.
This commit is contained in:
Jos Vooges | STH 2026-09-22 04:15:13 +02:00
parent 8685870358
commit 20a3cfb056
3 changed files with 35 additions and 6 deletions

View file

@ -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;

View file

@ -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: [],
};
}

View file

@ -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],
});