86962eb4bd
Deploy / deploy (push) Successful in 10m37s
Previews used to ship the full padded (handle-included) render to clients, with an artist manually pulling picture lock and hand-trimming the comp to match. The automated preview stage now trims the client comp to the picture-lock edit itself (a fixed handle count off each end, per the studio's existing convention) and separately builds an ungraded, full-duration picture-lock reference MP4 as a diagnostic sidecar to check back against. A new "No Picture Lock" panel checkbox is the single override for shots where picture lock is missing or wrong, falling back to today's untrimmed behavior. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
107 lines
4.2 KiB
TypeScript
107 lines
4.2 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(),
|
|
/** Bare footage root (e.g. "V:/_FOOTAGE") the panel's Pull Picture Lock reads
|
|
* from at `{footageRoot}/{showId}/_PICLOCKS` — same convention as the panel's
|
|
* own `getShowFootageRoot()`. */
|
|
footageRoot: 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;
|
|
footageRoot: 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, defaultFootageRoot] = await Promise.all([
|
|
getConfigValue("preview.templateAep"),
|
|
getConfigValue("preview.movTemplate"),
|
|
getConfigValue("preview.mp4Template"),
|
|
getConfigValue("preview.footageRoot"),
|
|
]);
|
|
|
|
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", ""),
|
|
footageRoot: pick("footageRoot", defaultFootageRoot),
|
|
overlayComp: pick("overlayComp", convention.overlayComp),
|
|
previewComp: pick("previewComp", convention.previewComp),
|
|
lutComp: pick("lutComp", convention.lutComp),
|
|
movTemplate: pick("movTemplate", defaultMov),
|
|
mp4Template: pick("mp4Template", defaultMp4),
|
|
explicit,
|
|
};
|
|
}
|