/** * Shot thumbnail resolution. * * A shot can carry two stills: * * • `thumbnailUrl` — the reference frame supplied with the brief * (client still, batch upload, ext API). Editorial * data: only ever written by a human. * • `generatedThumbnailUrl` — a frame promoted from a render export, either * automatically (projects with * `pipelineConfig.autoShotThumbnail`) or by hand. * * The reference always wins. Shows that arrive with stills look exactly as they * do today; shows that arrive without any get their empty grids filled from the * renders instead. Because the two live in different columns, a promotion can * never destroy the reference — clearing the generated one always falls back to * the brief. */ export interface ShotThumbnailFields { thumbnailUrl?: string | null; generatedThumbnailUrl?: string | null; } /** The still to display for a shot, or null when it has neither. */ export function shotThumbnail(shot: ShotThumbnailFields | null | undefined): string | null { return shot?.thumbnailUrl || shot?.generatedThumbnailUrl || null; } /** True when the displayed still came from a render rather than the brief. */ export function isGeneratedThumbnail(shot: ShotThumbnailFields | null | undefined): boolean { return !shot?.thumbnailUrl && !!shot?.generatedThumbnailUrl; } /** * Prisma `select` fragment for the two columns. Spread it wherever a query * already selects `thumbnailUrl` so the fallback has something to fall back to. */ export const shotThumbnailSelect = { thumbnailUrl: true, generatedThumbnailUrl: true, } as const;