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;
}
}