using System.Text.RegularExpressions; namespace VFXReviewWorker; /// /// Parses `blender -b` stdout. Blender reports the frame it is working on in /// its status lines ("Fra:1042 Mem:512.00M ...") and confirms each written file /// with "Saved: '...'". The saved lines are the reliable signal — a frame can /// be reported for a long time while it renders — so progress counts those and /// falls back to Fra: for a first sign of life. /// public static partial class BlenderProgressParser { [GeneratedRegex(@"^Fra:(\d+)", RegexOptions.Compiled)] private static partial Regex FrameLine(); [GeneratedRegex(@"^Saved:\s*'(.+)'", RegexOptions.Compiled)] private static partial Regex SavedLine(); [GeneratedRegex(@"^(Error|Warning): |^Error: ", RegexOptions.Compiled | RegexOptions.IgnoreCase)] private static partial Regex ErrorLine(); /// /// Failures that will repeat identically on another machine — a missing /// file, an unknown scene, an unwritable output. Retrying these burns the /// queue for nothing; a human has to fix the .blend or the path. /// private static readonly string[] NonRetryablePatterns = { "cannot read file", "file not found", "unable to open", "is not a blend file", "failed to read blend file", "unknown scene", "scene not found", "unable to create directory", "cannot write file", "no camera", "python script fail", "unknown argument", }; /// The frame Blender is currently working on, or null. public static int? ParseFrame(string line) { var m = FrameLine().Match(line); return m.Success && int.TryParse(m.Groups[1].Value, out var f) ? f : null; } /// The path of a frame Blender has just written, or null. public static string? ParseSaved(string line) { var m = SavedLine().Match(line); return m.Success ? m.Groups[1].Value : null; } public static bool IsErrorLine(string line) => ErrorLine().IsMatch(line); public static bool IsNonRetryableError(string line) { foreach (var p in NonRetryablePatterns) { if (line.Contains(p, StringComparison.OrdinalIgnoreCase)) return true; } return false; } }