diff --git a/Catalog.code-workspace b/Catalog.code-workspace index 36ebf33..15ec3a0 100644 --- a/Catalog.code-workspace +++ b/Catalog.code-workspace @@ -5,6 +5,9 @@ }, { "path": "../AIFotoONLUS" + }, + { + "path": "../../various/regalamiunsorriso" } ], "settings": { diff --git a/MaddoShared.Tests/DataModelCharacterizationTests.cs b/MaddoShared.Tests/DataModelCharacterizationTests.cs index 7ce601e..cba6ce0 100644 --- a/MaddoShared.Tests/DataModelCharacterizationTests.cs +++ b/MaddoShared.Tests/DataModelCharacterizationTests.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using System.Threading.Tasks; using ImageCatalog_2; using ImageCatalog_2.Services; @@ -134,6 +135,61 @@ public class DataModelCharacterizationTests model.FontSize.ShouldBe(42); } + [TestMethod] + public void FaceExecutableFolder_EnablesGpuToggleWhenBothVariantsExist() + { + using var root = new TemporaryDirectory(); + CreateFaceEncoderExecutable(root.Path, "cpu"); + CreateFaceEncoderExecutable(root.Path, "gpu"); + + var model = CreateModel(); + + model.FaceExecutablePath = root.Path; + + model.FaceGpuOptionEnabled.ShouldBeTrue(); + model.UseFaceGpu.ShouldBeFalse(); + } + + [TestMethod] + public void UseFaceGpu_UpdatesUpsampleWhenUsingRecommendedDefault() + { + using var root = new TemporaryDirectory(); + CreateFaceEncoderExecutable(root.Path, "cpu"); + CreateFaceEncoderExecutable(root.Path, "gpu"); + + var model = CreateModel(); + model.FaceExecutablePath = root.Path; + model.FaceUpsample.ShouldBeTrue(); + + model.UseFaceGpu = true; + + model.FaceUpsample.ShouldBeFalse(); + } + + [TestMethod] + public void ResolveConfiguredFaceEncoderExecutablePath_UsesFolderLayoutFromPowerShellScript() + { + using var root = new TemporaryDirectory(); + var cpuExecutable = CreateFaceEncoderExecutable(root.Path, "cpu"); + var gpuExecutable = CreateFaceEncoderExecutable(root.Path, "gpu"); + + DataModel.ResolveConfiguredFaceEncoderExecutablePath(root.Path, useGpu: false).ShouldBe(cpuExecutable); + DataModel.ResolveConfiguredFaceEncoderExecutablePath(root.Path, useGpu: true).ShouldBe(gpuExecutable); + } + + [TestMethod] + public void BuildFaceEncoderOutputPaths_UsesTimestampAndSanitizedFolderName() + { + var timestamp = new DateTime(2026, 5, 9, 14, 30, 45); + var output = DataModel.BuildFaceEncoderOutputPaths( + @"C:\out", + @"C:\images\04 APRILE: gara?", + timestamp); + + output.OutputFilePath.ShouldBe(@"C:\out\face_encodings_20260509_143045_04_APRILE_gara.pkl"); + output.LogFilePath.ShouldBe(@"C:\out\encoder_log_20260509_143045_04_APRILE_gara.txt"); + } + private static DataModel CreateModel( ISettingsService? settingsService = null, ITestService? testService = null) @@ -169,4 +225,33 @@ public class DataModelCharacterizationTests Substitute.For>(), versionProvider: null); } + + private static string CreateFaceEncoderExecutable(string rootPath, string variant) + { + var variantDirectory = Path.Combine(rootPath, $"face_encoder_{variant}"); + Directory.CreateDirectory(variantDirectory); + + var executablePath = Path.Combine(variantDirectory, $"face_encoder_{variant}.exe"); + File.WriteAllText(executablePath, "stub"); + return executablePath; + } + + private sealed class TemporaryDirectory : IDisposable + { + public TemporaryDirectory() + { + Path = System.IO.Path.Combine(System.IO.Path.GetTempPath(), System.IO.Path.GetRandomFileName()); + Directory.CreateDirectory(Path); + } + + public string Path { get; } + + public void Dispose() + { + if (Directory.Exists(Path)) + { + Directory.Delete(Path, recursive: true); + } + } + } } diff --git a/imagecatalog/AvaloniaViews/FaceAiTabView.axaml b/imagecatalog/AvaloniaViews/FaceAiTabView.axaml index 2beb773..134621c 100644 --- a/imagecatalog/AvaloniaViews/FaceAiTabView.axaml +++ b/imagecatalog/AvaloniaViews/FaceAiTabView.axaml @@ -5,16 +5,16 @@ - - + - - + + @@ -28,14 +28,24 @@ + + - + + + + + + + + @@ -49,11 +59,11 @@ - - - @@ -64,9 +74,22 @@ + -