Files
twotalesanimation cc89415a29 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>
2026-08-06 15:46:53 +02:00

37 lines
1.2 KiB
C#

namespace VFXReviewWorker;
/// <summary>
/// Translates canonical UNC paths from manifests to this machine's local
/// drive mappings (§7.8 pathMappings). Comparison is case-insensitive and
/// slash-direction tolerant; the first matching mapping wins.
/// </summary>
public sealed class PathMapper
{
private readonly List<(string From, string To)> _mappings;
public PathMapper(IEnumerable<PathMapping> mappings)
{
_mappings = mappings
.Where(m => !string.IsNullOrWhiteSpace(m.From))
.Select(m => (Normalize(m.From), m.To))
.ToList();
}
private static string Normalize(string p) => p.Replace('\\', '/');
public string Map(string path)
{
if (string.IsNullOrEmpty(path)) return path;
var normalized = Normalize(path);
foreach (var (from, to) in _mappings)
{
if (normalized.StartsWith(from, StringComparison.OrdinalIgnoreCase))
{
var mapped = to + normalized[from.Length..];
return mapped.Replace('/', Path.DirectorySeparatorChar);
}
}
return path.Replace('/', Path.DirectorySeparatorChar);
}
}