fix(pipeline): trim picture-lock handles at the footage comp, not the template's SHOT layer
Deploy / deploy (push) Successful in 6m17s
Deploy / deploy (push) Successful in 6m17s
Manipulating startTime/outPoint on previewComp's SHOT layer touched a comp duplicated from the artist-authored template, whose slate/shot timing relationship the build script doesn't own -- and it dropped the slate frame in production. The trim now happens one level down, on the private single-layer footage precomp this script fully controls; previewComp's own sizing logic is untouched from before this feature existed, since it already generalizes correctly to any shotComp duration. Also threads the picture-lock skip reason through to the export's timeline, so a shot missing sequence timecodes or a matching picture lock file is visible without digging into worker-machine logs. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -461,6 +461,7 @@ public sealed class JobExecutor
|
||||
["thumbnailKey"] = thumbKey,
|
||||
["piclockRefKey"] = piclockRefKey,
|
||||
},
|
||||
["piclockSkippedReason"] = result.Build?.PiclockSkippedReason,
|
||||
["renderStats"] = new Dictionary<string, object?>
|
||||
{
|
||||
["previewSeconds"] = (int)result.Seconds,
|
||||
|
||||
@@ -484,22 +484,56 @@
|
||||
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, duration, fps);
|
||||
ctx.shotCode + "_FOOTAGE", width, height, 1, contentDuration, fps);
|
||||
if (precompFolder) {
|
||||
footageComp.parentFolder = precompFolder;
|
||||
}
|
||||
if (ctx.frameStart !== null && ctx.frameStart !== undefined) {
|
||||
setCompStartFrame(footageComp, ctx.frameStart);
|
||||
if (contentStartFrame !== null) {
|
||||
setCompStartFrame(footageComp, contentStartFrame);
|
||||
}
|
||||
addOCIOToLayer(footageComp.layers.add(footage));
|
||||
var footageLayer = footageComp.layers.add(footage);
|
||||
if (headSkipSeconds > 0) {
|
||||
footageLayer.startTime -= headSkipSeconds;
|
||||
}
|
||||
addOCIOToLayer(footageLayer);
|
||||
|
||||
// 4. Shot comp: footage + show LUT + overlay
|
||||
var shotComp = app.project.items.addComp(ctx.shotCode, width, height, 1, duration, fps);
|
||||
if (ctx.frameStart !== null && ctx.frameStart !== undefined) {
|
||||
setCompStartFrame(shotComp, ctx.frameStart);
|
||||
var shotComp = app.project.items.addComp(ctx.shotCode, width, height, 1, contentDuration, fps);
|
||||
if (contentStartFrame !== null) {
|
||||
setCompStartFrame(shotComp, contentStartFrame);
|
||||
}
|
||||
shotComp.layers.add(footageComp);
|
||||
|
||||
@@ -573,31 +607,12 @@
|
||||
warn("NETFLIX_SLATE layer not found in template — slate fields not filled");
|
||||
}
|
||||
|
||||
// Trim the client-facing comp back to the picture-lock edit (§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. Both movOutput and mp4Output queue
|
||||
// against this same previewComp, so trimming it once fixes both.
|
||||
// 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 handleSeconds = (ctx.handleFrames || 0) / fps;
|
||||
var visibleDuration = shotComp.duration;
|
||||
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 >= shotComp.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 {
|
||||
shotLayer.startTime -= handleSeconds;
|
||||
visibleDuration = shotComp.duration - (2 * handleSeconds);
|
||||
trimmedToPictureLock = true;
|
||||
}
|
||||
|
||||
var targetOutPoint = shotLayer.inPoint + visibleDuration;
|
||||
var targetOutPoint = shotLayer.inPoint + shotComp.duration;
|
||||
if (targetOutPoint > previewComp.duration) {
|
||||
previewComp.duration = targetOutPoint;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user