diff --git a/RenderWorker/scripts/vfxr_build_preview.jsx b/RenderWorker/scripts/vfxr_build_preview.jsx index 960500c..15ce765 100644 --- a/RenderWorker/scripts/vfxr_build_preview.jsx +++ b/RenderWorker/scripts/vfxr_build_preview.jsx @@ -230,6 +230,26 @@ return names; } + // Slate identity fields (version name, shot name). Current templates expose + // these under a stable name, older ones only positionally, and the two do + // not always line up — so write both. Setting the same property twice is + // harmless; the panel uses the same name-with-index-fallback pattern. + function setSlateIdentity(layer, propName, index, value, label) { + var byName; + var byIndex; + + if (value === null || value === undefined || value === "") { + return; // leave the template's own default + } + byName = setEssentialProperty(layer, propName, value); + byIndex = setEssentialPropertyByIndex(layer, index, value); + if (!byName && !byIndex) { + warn(label + ": neither Essential Property \"" + propName + "\" nor index " + + index + " could be set. Available: " + + listEssentialPropertyNames(layer).join(", ")); + } + } + // Per-submission fields (VFX Scope / Submission Note). The property names // come from config; if one does not exist, report the template's actual // property names so it can be corrected without another build. @@ -439,12 +459,15 @@ var slateLayer = previewComp.layer("NETFLIX_SLATE"); if (slateLayer) { - // Index-addressed properties match the panel exactly (3 = version - // name, 10 = shot code); the rest are addressed by name. - setEssentialPropertyByIndex(slateLayer, 3, ctx.slate.versionName); + // Identity fields match the panel's indices (3 = version name, + // 10 = shot name) and are also written by name; the rest are + // addressed by name only. + // VersionName carries the full "{shotCode}_cmp_TT_{version}"; + // ShotName is the bare shot code, with no version suffix. + setSlateIdentity(slateLayer, "VersionName", 3, ctx.slate.versionName, "Version Name"); + setSlateIdentity(slateLayer, "ShotName", 10, ctx.slate.shotCode, "Shot Name"); setEssentialProperty(slateLayer, "Date", ctx.slate.date); setEssentialProperty(slateLayer, "Desc", ctx.slate.description); - setEssentialPropertyByIndex(slateLayer, 10, ctx.slate.shotCode); setEssentialProperty(slateLayer, "Episode", ctx.slate.episode); setEssentialProperty(slateLayer, "Scene", ctx.slate.scene); setEssentialProperty(slateLayer, "Frames", result.frameCount); diff --git a/lib/render-pipeline/preview.test.ts b/lib/render-pipeline/preview.test.ts new file mode 100644 index 0000000..f1ed659 --- /dev/null +++ b/lib/render-pipeline/preview.test.ts @@ -0,0 +1,32 @@ +import { describe, it, expect } from "vitest"; +import { nextTaskTitle } from "./preview"; + +describe("nextTaskTitle", () => { + it("swaps an existing version suffix", () => { + expect(nextTaskTitle("UNG_102_019_010_cmp_TT_v004", "v005")).toBe( + "UNG_102_019_010_cmp_TT_v005" + ); + }); + + it("is idempotent when the title already matches", () => { + expect(nextTaskTitle("UNG_102_019_010_cmp_TT_v005", "v005")).toBe( + "UNG_102_019_010_cmp_TT_v005" + ); + }); + + it("appends when the title carries no version yet", () => { + expect(nextTaskTitle("UNG_102_019_010_cmp_TT", "v005")).toBe( + "UNG_102_019_010_cmp_TT_v005" + ); + }); + + it("only touches a trailing suffix, not a version inside the name", () => { + expect(nextTaskTitle("UNG_v001_019_010_cmp_TT_v004", "v005")).toBe( + "UNG_v001_019_010_cmp_TT_v005" + ); + }); + + it("handles double-digit and uppercase suffixes", () => { + expect(nextTaskTitle("SHOT_cmp_TT_V12", "v013")).toBe("SHOT_cmp_TT_v013"); + }); +}); diff --git a/lib/render-pipeline/preview.ts b/lib/render-pipeline/preview.ts index cbb5407..bdbddf4 100644 --- a/lib/render-pipeline/preview.ts +++ b/lib/render-pipeline/preview.ts @@ -129,6 +129,16 @@ export async function createPreviewJob( }); } +/** + * `UNG_102_019_010_cmp_TT_v004` + v005 → `UNG_102_019_010_cmp_TT_v005`. + * Mirrors `nextExrOutputBase` (exports.ts) so the task title, the EXR base and + * the preview filename all carry the same version suffix. + */ +export function nextTaskTitle(current: string, versionString: string): string { + if (/_v\d+$/i.test(current)) return current.replace(/_v\d+$/i, `_${versionString}`); + return `${current}_${versionString}`; +} + export interface FinalizeInput { artifacts?: { deliveryMovPath?: string; @@ -167,6 +177,7 @@ export async function finalizePreview(jobId: string, machineId: string, body: Fi return { export: { id: exportRow.id, status: exportRow.status }, version: { id: exportRow.versionId }, + task: null, alreadyApplied: true, }; } @@ -227,6 +238,29 @@ export async function finalizePreview(jobId: string, machineId: string, body: Fi versionId = version.id; } + // The task title carries the version it is showing (house convention: + // "UNG_102_019_010_cmp_TT_v004"). The preview just uploaded *is* the task's + // new latest version, so the title has to follow the file — the same rename + // the manual batch upload does on "rename-and-upload". + let taskTitle: string | null = null; + let taskRenamed = false; + if (versionId && exportRow.taskId) { + const task = await tx.task.findUnique({ + where: { id: exportRow.taskId }, + select: { title: true }, + }); + if (task) { + taskTitle = nextTaskTitle(task.title, exportRow.versionString); + taskRenamed = taskTitle !== task.title; + if (taskRenamed) { + await tx.task.update({ + where: { id: exportRow.taskId }, + data: { title: taskTitle }, + }); + } + } + } + const updated = await applyTransition( tx, exportRow, @@ -234,7 +268,10 @@ export async function finalizePreview(jobId: string, machineId: string, body: Fi { type: "WORKER", id: machineId, - note: `Preview complete in ${body.renderStats?.previewSeconds ?? "?"}s${versionId ? " — review version created" : " — no preview media reported"}`, + note: + `Preview complete in ${body.renderStats?.previewSeconds ?? "?"}s` + + (versionId ? " — review version created" : " — no preview media reported") + + (taskRenamed ? ` — task renamed to ${taskTitle}` : ""), }, { ...(versionId ? { versionId } : {}), @@ -248,6 +285,7 @@ export async function finalizePreview(jobId: string, machineId: string, body: Fi return { export: { id: updated.id, status: updated.status }, version: versionId ? { id: versionId } : null, + task: exportRow.taskId ? { id: exportRow.taskId, title: taskTitle, renamed: taskRenamed } : null, alreadyApplied: false, }; });