Files
vfxreview/lib/render-pipeline/config.ts
T
twotalesanimation 9660cdc21c
Deploy / deploy (push) Successful in 3m23s
feat(review): High quality playback switch and per-version ProRes download
Worker 1.7.0 (preview.highQuality, default on) now, after the AE render:
- encodes a High quality MP4 from the delivery MOV (full-res H.264 High,
  CRF 14 capped at 100 Mbps) -> Version.hqFileUrl
- uploads the MOV itself -> Version.highResKey/Filename/Size
Both best-effort (the preview never fails over them), with a lease
keep-alive during the long encode/uploads; the export timeline records
what was attached.

Player: HQ switch when a High quality source exists; keeps frame, play
state, speed and zoom, remembered per browser, falls back to Standard if
the file won't load. ProRes itself stays download-only (browser support).

Downloads: the client portal's High Res button serves the version's
ProRes (client-code filename), falling back to the shot's manual file;
staff get a ProRes button on the review page.

Housekeeping (lib/render-pipeline/high-res.ts): HQ MP4 + ProRes are kept
while a version is latest, client-shared, approved or younger than
preview.highResRetentionDays (14); otherwise deleted from storage. Runs
per task after finalize and hourly from the reaper; deleting a version or
a reslate removes its files. The Standard MP4 is never touched.

Also: ProRes/Handles top-bar buttons go icon-only below lg so they don't
overlap the version selector.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 16:32:18 +02:00

135 lines
5.7 KiB
TypeScript

import { db } from "@/lib/db";
/**
* Pipeline-wide settings live in the existing SystemConfig key/value table
* (RenderPipeline2 §14.1) so the fleet can be tuned without redeploys.
*/
export const PIPELINE_CONFIG_DEFAULTS = {
"render.pollSeconds": 10,
"render.heartbeatSeconds": 30,
"render.leaseSeconds": 300,
"render.maxAttempts": 3,
"render.stallTimeoutSeconds": 600,
"render.urgentPriorityThreshold": 20,
/**
* How far a preview job outranks the render it follows. A preview is only
* created when its render finishes, so at equal priority it sorts behind
* every render queued alongside it — 10 shots run as 10 renders then 10
* previews, and nothing is reviewable until the end. A boost of 1 finishes
* shots end to end instead. Set to 0 for the old phase behaviour.
*/
"render.previewPriorityBoost": 1,
"render.outputRoot": "//SAN",
// Validation stage (§8). Disabling it restores the pre-Phase-3 flow
// (render → preview) without a redeploy, for a bad engine release.
"validation.enabled": "true",
"validation.sampleEvery": 25,
/** Per-check severity overrides, e.g. {"exr_type":"warn"} (§17.3). */
"validation.overrides": "{}",
/** Frames of head handle the plates carry; only used for the API timecode fallback (§8.1a). */
"validation.handleFrames": 8,
/**
* Studio-wide delivery defaults (§11.2). A project's own `deliveryConfig`
* column overrides these key by key; `{}` falls through to the built-ins in
* lib/render-pipeline/deliveries.ts.
*/
"delivery.defaultConfig": "{}",
"preview.maxWidth": 1920,
// Preview stage (§9): headless AE rebuild of the render with slate/burn-ins
"preview.enabled": "true",
"preview.templateAep": "X:/shared_projects_2026/UNGO_VFX/production/working_files/UNG_VFXOVERLAY_SLATE_TEMPLATE.aep",
"preview.templateComp": "UNG_EXPORT_TEMPLATE",
"preview.overlayComp": "UNG_VFX_OVERLAY",
"preview.lutComp": "_SHOW LUT",
"preview.movTemplate": "4444 Tri",
"preview.mp4Template": "REVIEW_PREVIEW",
// Essential Property names on the NETFLIX_SLATE layer for the per-submission
// fields. If a name is wrong the build logs the template's actual property
// names as a warning, so it is a config fix rather than a code change.
"preview.slateScopeProp": "Scope",
"preview.slateSubmissionProp": "Notes",
/**
* After the AE render, the worker also encodes a high-bitrate full-res MP4
* from the delivery MOV (the player's "High" quality) and uploads the MOV
* itself as the version's High Res download. Off = Standard MP4 only.
*/
"preview.highQuality": "true",
/**
* Housekeeping (lib/render-pipeline/high-res.ts): a version's High quality
* MP4 and ProRes are removed from storage once it is this many days old and
* nothing still needs them — not the latest, not shared, not approved.
*/
"preview.highResRetentionDays": 14,
/**
* When the dashboard starts calling something stuck. An export waiting on QC
* is normal for a day and suspicious after two; an export in a live render
* state has either progressed or died long before four hours are up.
*/
"dashboard.qcStaleHours": 48,
"dashboard.stalledJobHours": 4,
} as const;
export type PipelineConfigKey = keyof typeof PIPELINE_CONFIG_DEFAULTS;
export async function getConfigValue(key: PipelineConfigKey): Promise<string> {
const row = await db.systemConfig.findUnique({ where: { key } });
return row?.value ?? String(PIPELINE_CONFIG_DEFAULTS[key]);
}
export async function getConfigNumber(key: PipelineConfigKey): Promise<number> {
const raw = await getConfigValue(key);
const n = Number(raw);
return Number.isFinite(n) ? n : Number(PIPELINE_CONFIG_DEFAULTS[key]);
}
export async function getConfigBoolean(key: PipelineConfigKey): Promise<boolean> {
const raw = (await getConfigValue(key)).trim().toLowerCase();
return raw === "true" || raw === "1" || raw === "yes";
}
/** Preview-stage settings handed to the worker inside the PREVIEW_ONLY manifest. */
export async function getPreviewConfig() {
const [
enabled, templateAep, templateComp, overlayComp, lutComp, movTemplate, mp4Template,
slateScopeProp, slateSubmissionProp, highQuality,
] = await Promise.all([
getConfigBoolean("preview.enabled"),
getConfigValue("preview.templateAep"),
getConfigValue("preview.templateComp"),
getConfigValue("preview.overlayComp"),
getConfigValue("preview.lutComp"),
getConfigValue("preview.movTemplate"),
getConfigValue("preview.mp4Template"),
getConfigValue("preview.slateScopeProp"),
getConfigValue("preview.slateSubmissionProp"),
getConfigBoolean("preview.highQuality"),
]);
return {
enabled, templateAep, templateComp, overlayComp, lutComp, movTemplate, mp4Template,
slateScopeProp, slateSubmissionProp, highQuality,
};
}
/** Worker-facing config bundle returned by E6 registration. */
export async function getWorkerConfig() {
const [
pollSeconds, heartbeatSeconds, leaseSeconds, maxAttempts, stallTimeoutSeconds,
validationEnabled, validationSampleEvery, validationHandleFrames,
] = await Promise.all([
getConfigNumber("render.pollSeconds"),
getConfigNumber("render.heartbeatSeconds"),
getConfigNumber("render.leaseSeconds"),
getConfigNumber("render.maxAttempts"),
getConfigNumber("render.stallTimeoutSeconds"),
getConfigBoolean("validation.enabled"),
getConfigNumber("validation.sampleEvery"),
getConfigNumber("validation.handleFrames"),
]);
return {
pollSeconds, heartbeatSeconds, leaseSeconds, maxAttempts, stallTimeoutSeconds,
// Whether the validation stage runs is the server's call, so switching it
// off is a config change here rather than a redeploy of every worker (§8).
validationEnabled, validationSampleEvery, validationHandleFrames,
};
}