feat(pipeline): render queue, worker service and automated preview generation
One "Queue Export" click now renders the EXR sequence, then rebuilds the shot headlessly with the studio slate/overlay template to produce the delivery MOV and review MP4. Implements RenderPipeline2 phases 1-2 plus the preview stage. Server: - New models Export, RenderJob, ExportEvent, Machine, WorkerHeartbeat, plus Project.deliveryConfig and per-submission slate fields (Export.vfxScope, Export.submissionNote, inherited from the shot's previous export). Both migrations are purely additive; no existing column is touched. - lib/render-pipeline: server-enforced state machine, transactional version increment with supersede, atomic FOR UPDATE SKIP LOCKED claim gated by machine availability windows, and a lease reaper run from instrumentation.ts. - /api/ext/* endpoints for the panel and workers; session-auth mirrors under /api/render and /api/machines for the web UI. - Pipeline pages: render queue, export detail, machine monitoring, plus an Exports tab on shot detail. RenderWorker (.NET 8 Windows service, new): - Registration, heartbeat as cancel channel, claim loop, aerender runner with progress parsing and stall watchdog, crash recovery and disk-spooled reporting that survives server downtime. - Preview stage: headless AE assembles the preview comp into a throwaway AEP with both output modules queued, then a single aerender pass renders them. Preview jobs are not claimed while an interactive AE session is open, so an artist's project is never taken over. AE panel: Queue Export with live status polling, urgent flag, retry, and the VFX Scope / Submission Note fields. Every existing panel action is unchanged. Preview chaining ships disabled behind SystemConfig preview.enabled. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
/** Shared status→style maps for the pipeline pages. */
|
||||
|
||||
export const EXPORT_STATUS_STYLES: Record<string, string> = {
|
||||
QUEUED: "bg-zinc-500/15 text-zinc-300 border-zinc-500/30",
|
||||
RENDERING: "bg-blue-500/15 text-blue-400 border-blue-500/30",
|
||||
RENDER_FAILED: "bg-red-500/15 text-red-400 border-red-500/30",
|
||||
VALIDATING: "bg-sky-500/15 text-sky-400 border-sky-500/30",
|
||||
VALIDATION_FAILED: "bg-red-500/15 text-red-400 border-red-500/30",
|
||||
GENERATING_PREVIEW: "bg-indigo-500/15 text-indigo-400 border-indigo-500/30",
|
||||
PREVIEW_FAILED: "bg-red-500/15 text-red-400 border-red-500/30",
|
||||
READY_FOR_QC: "bg-amber-500/15 text-amber-400 border-amber-500/30",
|
||||
QC_FAILED: "bg-red-500/15 text-red-400 border-red-500/30",
|
||||
READY_FOR_DELIVERY: "bg-green-500/15 text-green-400 border-green-500/30",
|
||||
PACKAGED: "bg-emerald-500/15 text-emerald-400 border-emerald-500/30",
|
||||
DELIVERED: "bg-emerald-500/15 text-emerald-300 border-emerald-500/30",
|
||||
SUPERSEDED: "bg-zinc-500/10 text-zinc-500 border-zinc-600/30",
|
||||
ARCHIVED: "bg-zinc-500/10 text-zinc-500 border-zinc-600/30",
|
||||
CANCELLED: "bg-zinc-500/10 text-zinc-500 border-zinc-600/30",
|
||||
};
|
||||
|
||||
export const FAILED_STATUSES = ["RENDER_FAILED", "VALIDATION_FAILED", "PREVIEW_FAILED", "QC_FAILED"];
|
||||
export const ACTIVE_STATUSES = ["QUEUED", "RENDERING", "VALIDATING", "GENERATING_PREVIEW"];
|
||||
|
||||
export function statusLabel(status: string): string {
|
||||
return status.replaceAll("_", " ");
|
||||
}
|
||||
|
||||
export function formatEta(seconds: number | null | undefined): string {
|
||||
if (seconds == null || seconds <= 0) return "—";
|
||||
const m = Math.floor(seconds / 60);
|
||||
const s = Math.round(seconds % 60);
|
||||
return m > 0 ? `${m}m ${s}s` : `${s}s`;
|
||||
}
|
||||
Reference in New Issue
Block a user