using System.Text.Json; using System.Text.Json.Serialization; namespace VFXReviewWorker; /// /// Machine-local config (RenderPipeline2 ยง7.8) โ€” deliberately minimal; /// everything tunable lives in server SystemConfig and arrives via E6. /// Default path: C:\ProgramData\VFXReviewWorker\config.json /// public sealed class WorkerOptions { public string ServerUrl { get; set; } = ""; public string ApiKey { get; set; } = ""; public string MachineName { get; set; } = Environment.MachineName; public string AerenderPath { get; set; } = @"C:\Program Files\Adobe\Adobe After Effects 2026\Support Files\aerender.exe"; /// AfterFX.com โ€” the console AE used for the headless preview build. Defaults beside aerender. public string? AfterFxPath { get; set; } /// vfxr_build_preview.jsx โ€” defaults to scripts\ beside the worker exe. public string? PreviewScriptPath { get; set; } public string? FfmpegPath { get; set; } public string? OiiotoolPath { get; set; } public List PathMappings { get; set; } = new(); public string WorkerVersion { get; set; } = "1.0.0"; public string? AeVersion { get; set; } public static string DataDir => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "VFXReviewWorker"); public static string DefaultConfigPath => Path.Combine(DataDir, "config.json"); public static string LogsDir => Path.Combine(DataDir, "logs"); public static string SpoolDir => Path.Combine(DataDir, "spool"); public static string ScratchDir => Path.Combine(DataDir, "scratch"); public static string StateFilePath => Path.Combine(DataDir, "current-job.json"); /// AfterFX.com sits beside aerender.exe in a standard AE install. public string ResolveAfterFxPath() { if (!string.IsNullOrWhiteSpace(AfterFxPath)) return AfterFxPath; var dir = Path.GetDirectoryName(AerenderPath); return dir is null ? "AfterFX.com" : Path.Combine(dir, "AfterFX.com"); } public string ResolvePreviewScriptPath() { if (!string.IsNullOrWhiteSpace(PreviewScriptPath)) return PreviewScriptPath; return Path.Combine(AppContext.BaseDirectory, "scripts", "vfxr_build_preview.jsx"); } public static WorkerOptions Load(string? path = null) { path ??= DefaultConfigPath; if (!File.Exists(path)) { throw new FileNotFoundException( $"Worker config not found at {path}. Create it with serverUrl, apiKey and machineName (see RenderWorker/README.md)."); } var json = File.ReadAllText(path); var opts = JsonSerializer.Deserialize(json, JsonOpts) ?? throw new InvalidOperationException($"Could not parse {path}"); if (string.IsNullOrWhiteSpace(opts.ServerUrl)) throw new InvalidOperationException("config.json: serverUrl is required"); if (string.IsNullOrWhiteSpace(opts.ApiKey)) throw new InvalidOperationException("config.json: apiKey is required"); if (string.IsNullOrWhiteSpace(opts.MachineName)) opts.MachineName = Environment.MachineName; return opts; } public static readonly JsonSerializerOptions JsonOpts = new() { PropertyNameCaseInsensitive = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, }; } public sealed class PathMapping { public string From { get; set; } = ""; public string To { get; set; } = ""; }