revert(pipeline): back out picture-lock trimming from the preview build
Deploy / deploy (push) Successful in 6m19s
Deploy / deploy (push) Successful in 6m19s
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>
This commit is contained in:
@@ -30,9 +30,7 @@
|
||||
queued: [],
|
||||
frameCount: null,
|
||||
width: null,
|
||||
height: null,
|
||||
piclockRefBuilt: false,
|
||||
piclockSkippedReason: null
|
||||
height: null
|
||||
};
|
||||
var resultPath = null;
|
||||
|
||||
@@ -158,67 +156,6 @@
|
||||
return null;
|
||||
}
|
||||
|
||||
// Mirrors the panel's parseTimecodeToSeconds exactly.
|
||||
function parseTimecodeToSeconds(tc, fps) {
|
||||
var parts = String(tc).split(/[:;]/);
|
||||
if (parts.length < 4) {
|
||||
return 0;
|
||||
}
|
||||
return parseInt(parts[0], 10) * 3600 + parseInt(parts[1], 10) * 60 +
|
||||
parseInt(parts[2], 10) + parseInt(parts[3], 10) / (fps || 24);
|
||||
}
|
||||
|
||||
// Locates the picture-lock reference footage the same way the panel's
|
||||
// pullPictureLock does: the _PICLOCKS project folder first, then a disk
|
||||
// glob under ctx.piclockDir. Returns the FootageItem, or null (with a
|
||||
// warn()) if it cannot be found — never fatal to the preview build.
|
||||
function findPiclockFootage(ctx) {
|
||||
var piclocksProjectFolder = findFolder("_PICLOCKS");
|
||||
var i;
|
||||
var item;
|
||||
var piclocksDir;
|
||||
var files;
|
||||
var importOptions;
|
||||
|
||||
if (piclocksProjectFolder) {
|
||||
for (i = 1; i <= app.project.numItems; i += 1) {
|
||||
item = app.project.item(i);
|
||||
if (item instanceof FootageItem &&
|
||||
item.parentFolder === piclocksProjectFolder &&
|
||||
item.name.indexOf(ctx.piclockGlobPrefix + "_") === 0) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!ctx.piclockDir) {
|
||||
warn("Picture lock: no footage root configured for this project — skipping reference render");
|
||||
return null;
|
||||
}
|
||||
piclocksDir = new Folder(ctx.piclockDir);
|
||||
if (!piclocksDir.exists) {
|
||||
warn("Picture lock: folder not found: " + piclocksDir.fsName);
|
||||
return null;
|
||||
}
|
||||
files = piclocksDir.getFiles(ctx.piclockGlobPrefix + "_*");
|
||||
if (!files || files.length < 1) {
|
||||
warn("Picture lock: no file matching " + ctx.piclockGlobPrefix + "_* found in " + piclocksDir.fsName);
|
||||
return null;
|
||||
}
|
||||
for (i = 0; i < files.length; i += 1) {
|
||||
if (files[i] instanceof File) {
|
||||
try {
|
||||
importOptions = new ImportOptions(files[i]);
|
||||
return app.project.importFile(importOptions);
|
||||
} catch (importError) {
|
||||
warn("Picture lock: import failed: " + importError.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Mirrors the panel's addOCIOToLayer: effect added but disabled, so the
|
||||
// artist/farm chain matches exactly.
|
||||
function addOCIOToLayer(layer) {
|
||||
@@ -484,56 +421,22 @@
|
||||
result.height = height;
|
||||
result.frameCount = Math.round(duration * fps);
|
||||
|
||||
// Trim decision (§9, picture lock): the render's frame range is
|
||||
// editorial ± a fixed symmetric handle count by studio convention (the
|
||||
// same constant pullPictureLock hardcodes), so no picture-lock file is
|
||||
// needed for this — only ctx.handleFrames. Applied at the footage/shot
|
||||
// comp level (below) rather than by juggling layer timing inside
|
||||
// previewComp — previewComp is duplicated from an artist-authored
|
||||
// template whose slate/shot timing relationship this script does not
|
||||
// own, so it is left completely untouched, exactly as it always
|
||||
// rendered correctly before this feature existed.
|
||||
var handleSeconds = (ctx.handleFrames || 0) / fps;
|
||||
var contentDuration = duration;
|
||||
var headSkipSeconds = 0;
|
||||
var trimmedToPictureLock = false;
|
||||
if (ctx.noPictureLock) {
|
||||
result.piclockSkippedReason = "No Picture Lock was set for this export";
|
||||
} else if (handleSeconds <= 0) {
|
||||
result.piclockSkippedReason = "No handle frame count configured";
|
||||
} else if (2 * handleSeconds >= duration) {
|
||||
warn("Picture lock: handle count (" + ctx.handleFrames + "fr) covers the whole render (" +
|
||||
result.frameCount + "fr) — trim skipped");
|
||||
result.piclockSkippedReason = "handle count exceeds the render duration";
|
||||
} else {
|
||||
headSkipSeconds = handleSeconds;
|
||||
contentDuration = duration - (2 * handleSeconds);
|
||||
trimmedToPictureLock = true;
|
||||
}
|
||||
var contentStartFrame = (ctx.frameStart !== null && ctx.frameStart !== undefined)
|
||||
? ctx.frameStart + Math.round(headSkipSeconds * fps)
|
||||
: null;
|
||||
|
||||
// 3. Footage precomp (mirrors buildShotFromCode)
|
||||
var precompFolder = findFolder("_PRECOMPS");
|
||||
var footageComp = app.project.items.addComp(
|
||||
ctx.shotCode + "_FOOTAGE", width, height, 1, contentDuration, fps);
|
||||
ctx.shotCode + "_FOOTAGE", width, height, 1, duration, fps);
|
||||
if (precompFolder) {
|
||||
footageComp.parentFolder = precompFolder;
|
||||
}
|
||||
if (contentStartFrame !== null) {
|
||||
setCompStartFrame(footageComp, contentStartFrame);
|
||||
if (ctx.frameStart !== null && ctx.frameStart !== undefined) {
|
||||
setCompStartFrame(footageComp, ctx.frameStart);
|
||||
}
|
||||
var footageLayer = footageComp.layers.add(footage);
|
||||
if (headSkipSeconds > 0) {
|
||||
footageLayer.startTime -= headSkipSeconds;
|
||||
}
|
||||
addOCIOToLayer(footageLayer);
|
||||
addOCIOToLayer(footageComp.layers.add(footage));
|
||||
|
||||
// 4. Shot comp: footage + show LUT + overlay
|
||||
var shotComp = app.project.items.addComp(ctx.shotCode, width, height, 1, contentDuration, fps);
|
||||
if (contentStartFrame !== null) {
|
||||
setCompStartFrame(shotComp, contentStartFrame);
|
||||
var shotComp = app.project.items.addComp(ctx.shotCode, width, height, 1, duration, fps);
|
||||
if (ctx.frameStart !== null && ctx.frameStart !== undefined) {
|
||||
setCompStartFrame(shotComp, ctx.frameStart);
|
||||
}
|
||||
shotComp.layers.add(footageComp);
|
||||
|
||||
@@ -607,10 +510,6 @@
|
||||
warn("NETFLIX_SLATE layer not found in template — slate fields not filled");
|
||||
}
|
||||
|
||||
// previewComp sizes itself to whatever shotComp.duration actually is —
|
||||
// unchanged from before this feature existed. shotComp is already the
|
||||
// trimmed (or full, if skipped above) duration, so this needs no
|
||||
// special-casing here.
|
||||
var shotLayer = previewComp.layer("SHOT");
|
||||
var targetOutPoint = shotLayer.inPoint + shotComp.duration;
|
||||
if (targetOutPoint > previewComp.duration) {
|
||||
@@ -620,85 +519,13 @@
|
||||
previewComp.duration = targetOutPoint;
|
||||
|
||||
result.previewComp = previewComp.name;
|
||||
result.trimmedToPictureLock = trimmedToPictureLock;
|
||||
|
||||
// 6. Queue both outputs against the same preview comp. aerender will
|
||||
// render the whole queue in one launch.
|
||||
app.project.renderQueue.showWindow(false);
|
||||
while (app.project.renderQueue.numItems > 0) {
|
||||
app.project.renderQueue.item(1).remove();
|
||||
}
|
||||
|
||||
// 5b. Picture-lock reference comp: the raw plate only — no OCIO, no
|
||||
// show LUT, no overlay — spanning the FULL padded duration (handles
|
||||
// included), so a supervisor can check back against it if a delivered
|
||||
// edit is ever suspected to be off. Independent of the trim above:
|
||||
// this one needs the actual picture-lock file and sequence timecodes,
|
||||
// and is skipped gracefully (never fatal) if either is missing.
|
||||
if (ctx.noPictureLock) {
|
||||
if (!result.piclockSkippedReason) {
|
||||
result.piclockSkippedReason = "No Picture Lock was set for this export";
|
||||
}
|
||||
} else if (!ctx.seqTimecodeStart || !ctx.seqTimecodeEnd) {
|
||||
warn("Picture lock: no sequence timecodes for this shot — reference render skipped");
|
||||
result.piclockSkippedReason = "no sequence timecodes for this shot";
|
||||
} else {
|
||||
// A diagnostic aid, not a deliverable: any failure building or
|
||||
// queuing it must warn and move on, never take down the mov/mp4
|
||||
// that already succeeded (queueOutput() throws on template errors).
|
||||
try {
|
||||
var piclockFootage = findPiclockFootage(ctx);
|
||||
if (piclockFootage) {
|
||||
var piclockStartTC = 0;
|
||||
try {
|
||||
if (piclockFootage.mainSource && typeof piclockFootage.mainSource.startTimecode === "number") {
|
||||
piclockStartTC = piclockFootage.mainSource.startTimecode;
|
||||
}
|
||||
} catch (tcReadError) {
|
||||
}
|
||||
|
||||
var tcIn = parseTimecodeToSeconds(ctx.seqTimecodeStart, fps);
|
||||
var tcOut = parseTimecodeToSeconds(ctx.seqTimecodeEnd, fps);
|
||||
var refIn = tcIn - piclockStartTC - handleSeconds;
|
||||
var refOut = tcOut - piclockStartTC + handleSeconds;
|
||||
if (refIn < 0) {
|
||||
refIn = 0;
|
||||
}
|
||||
if (refOut > piclockFootage.duration) {
|
||||
refOut = piclockFootage.duration;
|
||||
}
|
||||
|
||||
var piclockRefComp = app.project.items.addComp(
|
||||
ctx.shotCode + "_PICLOCK_REF", width, height, 1, refOut - refIn, fps);
|
||||
if (previewsFolder) {
|
||||
piclockRefComp.parentFolder = previewsFolder;
|
||||
}
|
||||
var piclockLayer = piclockRefComp.layers.add(piclockFootage);
|
||||
var piclockScale = Math.max(
|
||||
piclockRefComp.width / piclockFootage.width,
|
||||
piclockRefComp.height / piclockFootage.height
|
||||
) * 100;
|
||||
piclockLayer.property("Scale").setValue([piclockScale, piclockScale]);
|
||||
piclockLayer.startTime = -refIn;
|
||||
piclockLayer.inPoint = 0;
|
||||
piclockLayer.outPoint = refOut - refIn;
|
||||
|
||||
if (queueOutput(piclockRefComp, ctx.mp4Template, ctx.piclockRefOutput, "piclockRef")) {
|
||||
result.piclockRefBuilt = true;
|
||||
} else {
|
||||
result.piclockSkippedReason = "no mp4 template or output path configured";
|
||||
}
|
||||
} else if (!result.piclockSkippedReason) {
|
||||
result.piclockSkippedReason = "picture-lock file not found";
|
||||
}
|
||||
} catch (piclockError) {
|
||||
warn("Picture lock: reference render skipped — " +
|
||||
(piclockError && piclockError.toString ? piclockError.toString() : String(piclockError)));
|
||||
result.piclockSkippedReason = "error building the reference render";
|
||||
result.piclockRefBuilt = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Queue both outputs against the same preview comp. aerender will
|
||||
// render the whole queue in one launch.
|
||||
queueOutput(previewComp, ctx.movTemplate, ctx.movOutput, "mov");
|
||||
queueOutput(previewComp, ctx.mp4Template, ctx.mp4Output, "mp4");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user