import { z } from "zod"; import { getConfigValue } from "./config"; /** * Per-production AE workspace configuration. * * Comp names follow a convention derived from the show — `UNG_VFX_OVERLAY`, * `UNG_EXPORT_TEMPLATE`, `_SHOW LUT` — so a project that names its template * comps to the pattern needs no configuration at all. Anything that cannot be * derived is stated explicitly: paths, above all, since a show's folder rarely * matches its show id (UNG lives under `UNGO_VFX`). * * Resolved server-side and handed to both the panel (via the ext projects list) * and the worker (inside the preview manifest), so the two cannot disagree * about which template a render was built from. */ export const pipelineConfigSchema = z.object({ /** The show's slate/overlay/LUT template AEP, imported by Initialise Workspace. */ templateAep: z.string().max(1000).optional().nullable(), /** Where per-shot pre-renders live, imported by Import Renders. */ rendersRoot: z.string().max(1000).optional().nullable(), /** Overrides for templates that depart from the {showId}_* convention. */ overlayComp: z.string().max(200).optional().nullable(), previewComp: z.string().max(200).optional().nullable(), lutComp: z.string().max(200).optional().nullable(), /** AE output-module templates, when this show does not use the studio defaults. */ movTemplate: z.string().max(200).optional().nullable(), mp4Template: z.string().max(200).optional().nullable(), /** * Promote each finished export's thumbnail onto its shot * (`Shot.generatedThumbnailUrl`). Off by default and deliberately per-show: * productions that ship a still with every shot already have a reference * frame, and the renders should not compete with it. Turn it on for shows * that arrive with no stills at all, where an empty grid is the alternative. * * Never touches `Shot.thumbnailUrl` — see lib/shot-thumbnail.ts. */ autoShotThumbnail: z.boolean().optional().nullable(), }); export type PipelineConfigInput = z.infer; export interface ResolvedTemplates { templateAep: string; rendersRoot: string; overlayComp: string; previewComp: string; lutComp: string; movTemplate: string; mp4Template: string; /** Which values came from the project rather than a convention or a default. */ explicit: string[]; } /** The comp-name convention. UNG's existing templates already match it exactly. */ export function conventionalNames(showId: string) { const prefix = showId?.trim() || "SHOW"; return { overlayComp: `${prefix}_VFX_OVERLAY`, previewComp: `${prefix}_EXPORT_TEMPLATE`, // The LUT comp is named the same in every show's template; what differs is // the LUT inside it, which is why the template AEP is per-project. lutComp: "_SHOW LUT", }; } /** * Whether finished exports promote their thumbnail onto the shot. Defaults to * false, including when `pipelineConfig` is malformed — a broken config must * not start rewriting shot thumbnails. */ export function autoShotThumbnailEnabled(project: { pipelineConfig?: unknown }): boolean { const parsed = pipelineConfigSchema.safeParse(project.pipelineConfig ?? {}); return parsed.success ? parsed.data.autoShotThumbnail === true : false; } export async function resolveProjectTemplates(project: { showId: string | null; code: string; pipelineConfig?: unknown; }): Promise { const parsed = pipelineConfigSchema.safeParse(project.pipelineConfig ?? {}); const cfg: PipelineConfigInput = parsed.success ? parsed.data : {}; if (!parsed.success) { console.error(`[render-pipeline] pipelineConfig for ${project.code} is malformed — using defaults`); } const showId = project.showId?.trim() || project.code; const convention = conventionalNames(showId); const explicit: string[] = []; const pick = (key: K, fallback: string): string => { const value = cfg[key]; if (typeof value === "string" && value.trim()) { explicit.push(key); return value.trim(); } return fallback; }; const [defaultAep, defaultMov, defaultMp4] = await Promise.all([ getConfigValue("preview.templateAep"), getConfigValue("preview.movTemplate"), getConfigValue("preview.mp4Template"), ]); return { templateAep: pick("templateAep", defaultAep), // No sensible studio-wide default: a renders root that points at another // show's folder is worse than an empty one the panel can report. rendersRoot: pick("rendersRoot", ""), overlayComp: pick("overlayComp", convention.overlayComp), previewComp: pick("previewComp", convention.previewComp), lutComp: pick("lutComp", convention.lutComp), movTemplate: pick("movTemplate", defaultMov), mp4Template: pick("mp4Template", defaultMp4), explicit, }; }