AI Pettorali
This commit is contained in:
parent
25fdb82d2f
commit
cb41c42bb5
11 changed files with 379 additions and 55 deletions
228
imagecatalog/CommandLineOperationRunner.cs
Normal file
228
imagecatalog/CommandLineOperationRunner.cs
Normal file
|
|
@ -0,0 +1,228 @@
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace ImageCatalog_2;
|
||||
|
||||
internal static class CommandLineOperationRunner
|
||||
{
|
||||
private static readonly HashSet<string> HeadlessMarkers = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"--operation",
|
||||
"--op",
|
||||
"--config",
|
||||
"--headless",
|
||||
"--cli"
|
||||
};
|
||||
|
||||
public static bool IsHeadlessRequest(string[]? args)
|
||||
{
|
||||
return args?.Any(arg => HeadlessMarkers.Contains(arg) || arg.StartsWith("--operation=", StringComparison.OrdinalIgnoreCase) || arg.StartsWith("--config=", StringComparison.OrdinalIgnoreCase)) == true;
|
||||
}
|
||||
|
||||
public static async Task<int> RunAsync(IServiceProvider services, string[] args)
|
||||
{
|
||||
var logger = services.GetRequiredService<ILoggerFactory>().CreateLogger("CommandLine");
|
||||
|
||||
try
|
||||
{
|
||||
var options = Parse(args);
|
||||
if (options.ShowHelp)
|
||||
{
|
||||
WriteUsage();
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(options.ConfigPath))
|
||||
{
|
||||
throw new ArgumentException("Missing required --config <path> argument.");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(options.Operation))
|
||||
{
|
||||
throw new ArgumentException("Missing required --operation <image-processing|number-ai|face-ai|race-upload> argument.");
|
||||
}
|
||||
|
||||
if (!File.Exists(options.ConfigPath))
|
||||
{
|
||||
throw new FileNotFoundException("Configuration file not found.", options.ConfigPath);
|
||||
}
|
||||
|
||||
var model = services.GetRequiredService<DataModel>();
|
||||
await model.LoadSettingsFromFileAsync(options.ConfigPath).ConfigureAwait(false);
|
||||
ApplyOverrides(model, options);
|
||||
|
||||
using var cancellationTokenSource = new CancellationTokenSource();
|
||||
Console.CancelKeyPress += (_, eventArgs) =>
|
||||
{
|
||||
eventArgs.Cancel = true;
|
||||
cancellationTokenSource.Cancel();
|
||||
};
|
||||
|
||||
logger.LogInformation("Running ImageCatalog operation {Operation} with config {ConfigPath}", options.Operation, options.ConfigPath);
|
||||
|
||||
switch (NormalizeOperation(options.Operation))
|
||||
{
|
||||
case "image-processing":
|
||||
await model.ProcessImages().ConfigureAwait(false);
|
||||
break;
|
||||
case "number-ai":
|
||||
await model.RunNumberAiAsync(cancellationTokenSource.Token).ConfigureAwait(false);
|
||||
break;
|
||||
case "face-ai":
|
||||
await model.RunFaceAiAsync(cancellationTokenSource.Token).ConfigureAwait(false);
|
||||
break;
|
||||
case "race-upload":
|
||||
throw new NotSupportedException("race-upload is not available in headless mode yet because the upload workflow still lives in the Avalonia view layer.");
|
||||
default:
|
||||
throw new ArgumentException($"Unknown operation: {options.Operation}");
|
||||
}
|
||||
|
||||
logger.LogInformation("ImageCatalog operation {Operation} completed", options.Operation);
|
||||
return 0;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
logger.LogWarning("Command-line operation canceled.");
|
||||
return 130;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Command-line operation failed.");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
private static void ApplyOverrides(DataModel model, CommandLineOptions options)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(options.ModelsPath))
|
||||
{
|
||||
model.ModelsFolderPath = options.ModelsPath;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(options.CsvPath))
|
||||
{
|
||||
model.CsvOutputPath = options.CsvPath;
|
||||
}
|
||||
|
||||
if (options.UseGpu.HasValue)
|
||||
{
|
||||
model.UseNumberAiGpu = options.UseGpu.Value;
|
||||
if (model.FaceGpuOptionEnabled || !options.UseGpu.Value)
|
||||
{
|
||||
model.UseFaceGpu = options.UseGpu.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string NormalizeOperation(string operation)
|
||||
{
|
||||
return operation.Trim().ToLowerInvariant() switch
|
||||
{
|
||||
"images" or "image" or "process-images" or "image-processing" => "image-processing",
|
||||
"ai" or "ocr" or "number" or "number-ai" => "number-ai",
|
||||
"face" or "face-ai" => "face-ai",
|
||||
"race" or "race-upload" => "race-upload",
|
||||
var normalized => normalized
|
||||
};
|
||||
}
|
||||
|
||||
private static CommandLineOptions Parse(string[] args)
|
||||
{
|
||||
var options = new CommandLineOptions();
|
||||
|
||||
for (var i = 0; i < args.Length; i++)
|
||||
{
|
||||
var arg = args[i];
|
||||
switch (arg.ToLowerInvariant())
|
||||
{
|
||||
case "--help":
|
||||
case "-h":
|
||||
case "/?":
|
||||
options.ShowHelp = true;
|
||||
break;
|
||||
case "--operation":
|
||||
case "--op":
|
||||
options.Operation = ReadValue(args, ref i, arg);
|
||||
break;
|
||||
case "--config":
|
||||
options.ConfigPath = ReadValue(args, ref i, arg);
|
||||
break;
|
||||
case "--models":
|
||||
options.ModelsPath = ReadValue(args, ref i, arg);
|
||||
break;
|
||||
case "--csv":
|
||||
options.CsvPath = ReadValue(args, ref i, arg);
|
||||
break;
|
||||
case "--gpu":
|
||||
options.UseGpu = true;
|
||||
break;
|
||||
case "--cpu":
|
||||
options.UseGpu = false;
|
||||
break;
|
||||
case "--headless":
|
||||
case "--cli":
|
||||
break;
|
||||
default:
|
||||
ApplyInlineArgument(options, arg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
private static void ApplyInlineArgument(CommandLineOptions options, string arg)
|
||||
{
|
||||
var separatorIndex = arg.IndexOf('=');
|
||||
if (separatorIndex < 0)
|
||||
{
|
||||
throw new ArgumentException($"Unknown argument: {arg}");
|
||||
}
|
||||
|
||||
var name = arg[..separatorIndex].ToLowerInvariant();
|
||||
var value = arg[(separatorIndex + 1)..];
|
||||
switch (name)
|
||||
{
|
||||
case "--operation":
|
||||
case "--op":
|
||||
options.Operation = value;
|
||||
break;
|
||||
case "--config":
|
||||
options.ConfigPath = value;
|
||||
break;
|
||||
case "--models":
|
||||
options.ModelsPath = value;
|
||||
break;
|
||||
case "--csv":
|
||||
options.CsvPath = value;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException($"Unknown argument: {arg}");
|
||||
}
|
||||
}
|
||||
|
||||
private static string ReadValue(string[] args, ref int index, string name)
|
||||
{
|
||||
if (index + 1 >= args.Length)
|
||||
{
|
||||
throw new ArgumentException($"Missing value for {name}.");
|
||||
}
|
||||
|
||||
return args[++index];
|
||||
}
|
||||
|
||||
private static void WriteUsage()
|
||||
{
|
||||
Console.WriteLine("Usage: ImageCatalog --config <settings.xml> --operation <image-processing|number-ai|face-ai|race-upload> [--models <folder>] [--csv <path>] [--cpu|--gpu]");
|
||||
}
|
||||
|
||||
private sealed class CommandLineOptions
|
||||
{
|
||||
public bool ShowHelp { get; set; }
|
||||
public string Operation { get; set; } = string.Empty;
|
||||
public string ConfigPath { get; set; } = string.Empty;
|
||||
public string ModelsPath { get; set; } = string.Empty;
|
||||
public string CsvPath { get; set; } = string.Empty;
|
||||
public bool? UseGpu { get; set; }
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue