Files
vfxreview/lib/render-pipeline/project-templates.ts
T
twotalesanimation a18dfc6203
Deploy / deploy (push) Successful in 6m19s
revert(pipeline): back out picture-lock trimming from the preview build
Production fallout after enabling it: the trimmed EXR footage started
after the head handle as intended, but the overlay's burn-in timecode
(hardcoded to start at frame 1001) did not shift with it, so the
delivered burn-in no longer matched the footage. The picture-lock
reference render also never worked correctly in practice. Reverts the
AE panel checkbox, the preview-build script's trim/reference logic, and
the server/worker plumbing around it back to their pre-feature state
(82dfaee) — untrimmed previews, no picture-lock reference, overlay
timecode correct again.

The noPictureLock/piclockRefKey schema columns and their migration are
deliberately left in place: they're already applied in production, and
dropping an applied migration's file risks the next `prisma migrate
deploy`. They sit unused and harmless until this is revisited.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-23 21:44:45 +02:00

100 lines
3.8 KiB
TypeScript

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(),
});
export type PipelineConfigInput = z.infer<typeof pipelineConfigSchema>;
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",
};
}
export async function resolveProjectTemplates(project: {
showId: string | null;
code: string;
pipelineConfig?: unknown;
}): Promise<ResolvedTemplates> {
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 = <K extends keyof PipelineConfigInput>(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,
};
}