using System.Text.Json; using System.Text.Json.Serialization; namespace VFXReviewWorker; // Wire types for the /api/ext pipeline endpoints (RenderPipeline2 §6). public sealed class RegisterResponse { public MachineInfo Machine { get; set; } = new(); public ServerConfig Config { get; set; } = new(); } public sealed class MachineInfo { public string Id { get; set; } = ""; public string Name { get; set; } = ""; public bool Enabled { get; set; } } /// Server-supplied tuning (E6) — fleet-tunable without touching installs. public sealed class ServerConfig { public int PollSeconds { get; set; } = 10; public int HeartbeatSeconds { get; set; } = 30; public int LeaseSeconds { get; set; } = 300; public int MaxAttempts { get; set; } = 3; public int StallTimeoutSeconds { get; set; } = 600; /// /// Validation stage (§8). The server decides whether it runs, so turning it /// off is a config change there rather than a redeploy of every worker. /// public bool ValidationEnabled { get; set; } = true; public int ValidationSampleEvery { get; set; } = 25; /// Head handles the plates carry; only used for the API timecode fallback (§8.1a). public int ValidationHandleFrames { get; set; } = 8; } public sealed class ClaimResponse { public ClaimedJob Job { get; set; } = new(); } public sealed class ClaimedJob { public string Id { get; set; } = ""; public string Type { get; set; } = "AE_RENDER"; public int Attempt { get; set; } public int MaxAttempts { get; set; } public string? ExportId { get; set; } public string? DeliveryId { get; set; } /// Set for jobs with no shot behind them; shown instead of a shot code. public string? Label { get; set; } public DateTimeOffset? LeaseExpiresAt { get; set; } /// Manifest shape depends on the job type — deserialize per stage. public JsonElement Manifest { get; set; } public RenderManifest AsRenderManifest() => Manifest.Deserialize(WorkerOptions.JsonOpts) ?? throw new InvalidOperationException("Job manifest is not a render manifest"); public PreviewManifest AsPreviewManifest() => Manifest.Deserialize(WorkerOptions.JsonOpts) ?? throw new InvalidOperationException("Job manifest is not a preview manifest"); public DeliveryManifest AsDeliveryManifest() => Manifest.Deserialize(WorkerOptions.JsonOpts) ?? throw new InvalidOperationException("Job manifest is not a delivery manifest"); public BlenderManifest AsBlenderManifest() => Manifest.Deserialize(WorkerOptions.JsonOpts) ?? throw new InvalidOperationException("Job manifest is not a Blender manifest"); } /// /// A standalone Blender render. Overrides are all optional: an unset value /// leaves whatever the .blend was saved with, which is the point — the file is /// expected to be ready to render. /// public sealed class BlenderManifest { public string Stage { get; set; } = "BLENDER"; public string BlendFile { get; set; } = ""; public string? Scene { get; set; } public int FrameStart { get; set; } public int FrameEnd { get; set; } public string? OutputPath { get; set; } public string? Engine { get; set; } public string? Device { get; set; } public int? Samples { get; set; } public int? ResolutionPercent { get; set; } public string? BlenderVersion { get; set; } public int TotalFrames => FrameEnd - FrameStart + 1; } /// DELIVERY_BUILD manifest (server-built, RenderPipeline2 §11). public sealed class DeliveryManifest { public string Stage { get; set; } = "DELIVERY"; /// /// "EXR_DELIVERY" builds the per-shot folders of EXRs + MOVs; "MOV_REVIEW" /// builds a flat folder of preview MOVs for a formal review (§11.6). One /// job type and one claim path either way — only what lands on disk differs. /// public string Kind { get; set; } = "EXR_DELIVERY"; public string PackageId { get; set; } = ""; public string PackageName { get; set; } = ""; public int PackageNumber { get; set; } public string Episode { get; set; } = ""; public string PackageDir { get; set; } = ""; public string ReportFile { get; set; } = ""; public bool IncludeMovs { get; set; } = true; public string CopyMode { get; set; } = "auto"; /// "auto" verifies copies by hash and links by identity; "always-hash" reads everything back. public string VerifyMode { get; set; } = "auto"; public string ChecksumAlgo { get; set; } = "xxh64"; public List ReportFormats { get; set; } = new(); public List Items { get; set; } = new(); } public sealed class DeliveryManifestItem { public string ItemId { get; set; } = ""; public string ExportId { get; set; } = ""; public string ShotCode { get; set; } = ""; public string VersionString { get; set; } = ""; public string SourceDir { get; set; } = ""; public string OutputPattern { get; set; } = ""; public int FrameStart { get; set; } public int FrameEnd { get; set; } public int? FileCount { get; set; } public string? Checksum { get; set; } public string? DeliveryMovPath { get; set; } public string ShotDir { get; set; } = ""; /// /// Server-computed: "{clientCode}_{versionString}" when the shot has a /// client naming code, else the studio's own "{shotCode}_cmp_TT_{versionString}". /// The delivered EXR sequence and MOV are named from this, not the /// internal render filename they're copied from. /// public string DeliveredBaseName { get; set; } = ""; /// When this export's validation ran — lets a linked package detect a source changed since QC. public DateTimeOffset? ValidatedAt { get; set; } /// /// True on a MOV_REVIEW item: there is no EXR sequence, so SourceDir, /// OutputPattern, FileCount and Checksum are empty and DeliveryMovPath is /// the whole deliverable. /// public bool IsMovOnly => string.IsNullOrWhiteSpace(SourceDir); } /// PREVIEW_ONLY manifest (server-built, RenderPipeline2 §9). public sealed class PreviewManifest { public string Stage { get; set; } = "PREVIEW"; public string ExportId { get; set; } = ""; public string ShotCode { get; set; } = ""; public string VersionString { get; set; } = ""; public string OutputDir { get; set; } = ""; public string OutputPattern { get; set; } = ""; public int FrameStart { get; set; } public int FrameEnd { get; set; } public double Fps { get; set; } = 24; public string TemplateAep { get; set; } = ""; public string TemplateComp { get; set; } = ""; public string? OverlayComp { get; set; } public string? LutComp { get; set; } public string MovTemplate { get; set; } = ""; public string Mp4Template { get; set; } = ""; public string MovOutput { get; set; } = ""; public string Mp4Output { get; set; } = ""; public string? SlateScopeProp { get; set; } public string? SlateSubmissionProp { get; set; } public SlateInfo Slate { get; set; } = new(); public int TotalFrames => FrameEnd - FrameStart + 1; } public sealed class SlateInfo { public string VersionName { get; set; } = ""; public string Date { get; set; } = ""; public string? Description { get; set; } public string? Notes { get; set; } public string ShotCode { get; set; } = ""; /// Production name, burned into the slate and the overlay. public string? Show { get; set; } public string? Episode { get; set; } public string? Scene { get; set; } public string? VfxScope { get; set; } public string? SubmissionNote { get; set; } } /// Result JSON written by vfxr_build_preview.jsx. public sealed class PreviewBuildResult { public bool Ok { get; set; } public string? Error { get; set; } public List Warnings { get; set; } = new(); public string? TempAep { get; set; } public string? PreviewComp { get; set; } public int? FrameCount { get; set; } public int? Width { get; set; } public int? Height { get; set; } /// /// Seconds of slate ahead of picture in the preview comp (the SHOT layer's /// in-point). Null on builds predating this field, or when the template has /// no SHOT layer — the thumbnail then falls back to scanning from frame 0. /// public double? PictureStartSeconds { get; set; } } /// The Render Manifest (§6.2) — the fields the render and validation stages need; the rest is preserved raw. public sealed class RenderManifest { public string ShotCode { get; set; } = ""; public string AepPath { get; set; } = ""; public string CompName { get; set; } = ""; public string OutputDir { get; set; } = ""; public string OutputPattern { get; set; } = ""; public string? OutputModuleTemplate { get; set; } public string? RenderSettingsTemplate { get; set; } /// Comp timeline range handed to aerender (-s/-e). public int FrameStart { get; set; } public int FrameEnd { get; set; } /// /// The number the *first output file* carries — the output module's /// starting number (house convention 1001), which is deliberately not the /// comp's frame range: a comp anchored to the plate's timecode starts in /// the millions. Null on manifests predating this field. /// public int? OutputFrameStart { get; set; } public double Fps { get; set; } = 24; public int Width { get; set; } public int Height { get; set; } public ManifestExpected? Expected { get; set; } public ManifestSource? Source { get; set; } [JsonExtensionData] public Dictionary? Extra { get; set; } public int TotalFrames => FrameEnd - FrameStart + 1; } /// Per-project expectations the validation engine checks against (§8.1). public sealed class ManifestExpected { public string? Colorspace { get; set; } public string? BitDepth { get; set; } public string? ExrCompression { get; set; } public bool? Alpha { get; set; } /// The API's editorial in-point — the rank 3 timecode fallback only (§8.1a). public string? TimecodeStart { get; set; } } /// Plate provenance resolved by the panel from the comp's footage layers (§6.2). public sealed class ManifestSource { public PlateRef? TimingPlate { get; set; } public List Plates { get; set; } = new(); public string? StartSource { get; set; } public int? StartFrames { get; set; } public int? HandleFrames { get; set; } } public sealed class PlateRef { public string? Element { get; set; } public string Path { get; set; } = ""; public int? FrameStart { get; set; } public int? FrameEnd { get; set; } } public sealed class ProgressResponse { public bool Ok { get; set; } public bool CancelRequested { get; set; } public DateTimeOffset? LeaseExpiresAt { get; set; } } public sealed class HeartbeatResponse { public bool Ok { get; set; } public List Commands { get; set; } = new(); } public sealed class WorkerCommand { public string Type { get; set; } = ""; public string? JobId { get; set; } } public sealed class PresignResponse { public string PresignedUrl { get; set; } = ""; public string Key { get; set; } = ""; public string Url { get; set; } = ""; } public sealed class ExportDetailResponse { public ExportDetail Export { get; set; } = new(); } public sealed class ExportDetail { public string Id { get; set; } = ""; public string Status { get; set; } = ""; public List RenderJobs { get; set; } = new(); } public sealed class RenderJobDetail { public string Id { get; set; } = ""; public string Status { get; set; } = ""; } /// Durable lifecycle report persisted to the spool directory (§7.6). public sealed class SpooledReport { public string Kind { get; set; } = ""; // "fail" | "complete-render" public string JobId { get; set; } = ""; public Dictionary Payload { get; set; } = new(); public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow; } /// current-job.json — crash recovery state (§7.5). public sealed class CurrentJobState { public string JobId { get; set; } = ""; public string? ExportId { get; set; } = ""; public string MachineId { get; set; } = ""; public string OutputDirLocal { get; set; } = ""; public DateTimeOffset ClaimedAt { get; set; } }