- Implement GeneralTabView for source and destination path settings, options, processing parameters, and image library selection. - Create LogoTabView for logo selection, preview, and positioning options. - Add PhotoTabView for photo dimensions and JPEG quality settings. - Introduce RaceUploadTabView for race setup and processed photo upload functionality, including API integration. - Develop TextTabView for horizontal and vertical text settings, font options, and race time configuration. - Implement ThumbnailsTabView for thumbnail creation options and settings.
317 lines
9.9 KiB
C#
317 lines
9.9 KiB
C#
using Avalonia.Controls;
|
|
using Avalonia.Interactivity;
|
|
using Avalonia.Platform.Storage;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using System;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace ImageCatalog_2.AvaloniaViews;
|
|
|
|
public partial class FaceAiTabView : Avalonia.Controls.UserControl
|
|
{
|
|
private readonly ILogger<FaceAiTabView> _logger;
|
|
|
|
public FaceAiTabView()
|
|
{
|
|
InitializeComponent();
|
|
_logger = Program.ServiceProvider.GetService(typeof(ILogger<FaceAiTabView>)) as ILogger<FaceAiTabView>
|
|
?? NullLogger<FaceAiTabView>.Instance;
|
|
}
|
|
|
|
private async void SelectFaceExecutable_Click(object? sender, RoutedEventArgs e)
|
|
{
|
|
var executableBox = this.FindControl<Avalonia.Controls.TextBox>("FaceExecutablePathTextBox");
|
|
if (executableBox is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var topLevel = TopLevel.GetTopLevel(this);
|
|
var storageProvider = topLevel?.StorageProvider;
|
|
if (storageProvider is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var files = await storageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
|
|
{
|
|
Title = "Seleziona face_encoder.exe",
|
|
FileTypeFilter =
|
|
[
|
|
new FilePickerFileType("Eseguibile") { Patterns = ["*.exe"] },
|
|
new FilePickerFileType("Tutti i file") { Patterns = ["*.*"] }
|
|
]
|
|
});
|
|
|
|
if (files.Count > 0)
|
|
{
|
|
executableBox.Text = files[0].Path.LocalPath;
|
|
if (DataContext is DataModel model)
|
|
{
|
|
model.FaceExecutablePath = executableBox.Text;
|
|
}
|
|
}
|
|
}
|
|
|
|
private async void SelectFaceOutputFolder_Click(object? sender, RoutedEventArgs e)
|
|
{
|
|
var outputBox = this.FindControl<Avalonia.Controls.TextBox>("FaceOutputFolderTextBox");
|
|
if (outputBox is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var topLevel = TopLevel.GetTopLevel(this);
|
|
var storageProvider = topLevel?.StorageProvider;
|
|
if (storageProvider is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var folders = await storageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
|
|
{
|
|
Title = "Seleziona cartella output encodings"
|
|
});
|
|
|
|
if (folders.Count > 0)
|
|
{
|
|
outputBox.Text = folders[0].Path.LocalPath;
|
|
if (DataContext is DataModel model)
|
|
{
|
|
model.FaceOutputFolderPath = outputBox.Text;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OpenFaceExecutableFolder_Click(object? sender, RoutedEventArgs e)
|
|
{
|
|
var executableBox = this.FindControl<Avalonia.Controls.TextBox>("FaceExecutablePathTextBox");
|
|
if (executableBox is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var path = executableBox.Text?.Trim();
|
|
if (string.IsNullOrWhiteSpace(path))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (File.Exists(path))
|
|
{
|
|
OpenInExplorer(path);
|
|
return;
|
|
}
|
|
|
|
var directory = Path.GetDirectoryName(path);
|
|
OpenInExplorer(string.IsNullOrWhiteSpace(directory) ? path : directory);
|
|
}
|
|
|
|
private void OpenFaceOutputFolder_Click(object? sender, RoutedEventArgs e)
|
|
{
|
|
var outputBox = this.FindControl<Avalonia.Controls.TextBox>("FaceOutputFolderTextBox");
|
|
if (outputBox is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
OpenInExplorer(outputBox.Text);
|
|
}
|
|
|
|
private async void RunFaceEncoder_Click(object? sender, RoutedEventArgs e)
|
|
{
|
|
var executableBox = this.FindControl<Avalonia.Controls.TextBox>("FaceExecutablePathTextBox");
|
|
var outputFolderBox = this.FindControl<Avalonia.Controls.TextBox>("FaceOutputFolderTextBox");
|
|
var outputLogBox = this.FindControl<Avalonia.Controls.TextBox>("FaceOutputTextBox");
|
|
var statusBlock = this.FindControl<TextBlock>("FaceStatusTextBlock");
|
|
var runButton = this.FindControl<Avalonia.Controls.Button>("FaceRunButton");
|
|
|
|
if (executableBox is null || outputFolderBox is null || outputLogBox is null || statusBlock is null || runButton is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (DataContext is not DataModel model)
|
|
{
|
|
statusBlock.Text = "DataContext non valido.";
|
|
return;
|
|
}
|
|
|
|
var executablePath = executableBox.Text?.Trim().Trim('"') ?? string.Empty;
|
|
var outputFolder = outputFolderBox.Text?.Trim().Trim('"') ?? string.Empty;
|
|
var imagesFolder = (model.DestinationPath ?? string.Empty).Trim().Trim('"');
|
|
|
|
model.FaceExecutablePath = executablePath;
|
|
model.FaceOutputFolderPath = outputFolder;
|
|
|
|
if (string.IsNullOrWhiteSpace(executablePath) || !File.Exists(executablePath))
|
|
{
|
|
statusBlock.Text = "Percorso eseguibile non valido.";
|
|
return;
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(imagesFolder) || !Directory.Exists(imagesFolder))
|
|
{
|
|
statusBlock.Text = "Cartella Destinazione non valida.";
|
|
return;
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(outputFolder))
|
|
{
|
|
statusBlock.Text = "Inserisci la cartella di output.";
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
Directory.CreateDirectory(outputFolder);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Unable to create face output folder: {OutputFolder}", outputFolder);
|
|
statusBlock.Text = "Impossibile creare la cartella di output.";
|
|
return;
|
|
}
|
|
|
|
runButton.IsEnabled = false;
|
|
statusBlock.Text = "Esecuzione face encoder in corso...";
|
|
outputLogBox.Text = string.Empty;
|
|
|
|
var outputLines = new StringBuilder();
|
|
var errorLines = new StringBuilder();
|
|
|
|
try
|
|
{
|
|
var imagesFolderArg = NormalizeDirectoryPathArgument(imagesFolder);
|
|
var outputFolderArg = NormalizeDirectoryPathArgument(outputFolder);
|
|
|
|
var processStartInfo = new ProcessStartInfo
|
|
{
|
|
FileName = executablePath,
|
|
WorkingDirectory = Path.GetDirectoryName(executablePath) ?? Environment.CurrentDirectory,
|
|
UseShellExecute = false,
|
|
RedirectStandardOutput = true,
|
|
RedirectStandardError = true,
|
|
CreateNoWindow = true,
|
|
};
|
|
processStartInfo.ArgumentList.Add("--images");
|
|
processStartInfo.ArgumentList.Add(imagesFolderArg);
|
|
processStartInfo.ArgumentList.Add("--out");
|
|
processStartInfo.ArgumentList.Add(outputFolderArg);
|
|
|
|
using var process = new Process { StartInfo = processStartInfo, EnableRaisingEvents = true };
|
|
process.OutputDataReceived += (_, args) =>
|
|
{
|
|
if (string.IsNullOrWhiteSpace(args.Data))
|
|
{
|
|
return;
|
|
}
|
|
|
|
lock (outputLines)
|
|
{
|
|
outputLines.AppendLine(args.Data);
|
|
}
|
|
};
|
|
|
|
process.ErrorDataReceived += (_, args) =>
|
|
{
|
|
if (string.IsNullOrWhiteSpace(args.Data))
|
|
{
|
|
return;
|
|
}
|
|
|
|
lock (errorLines)
|
|
{
|
|
errorLines.AppendLine(args.Data);
|
|
}
|
|
};
|
|
|
|
if (!process.Start())
|
|
{
|
|
throw new InvalidOperationException("Avvio face_encoder.exe fallito.");
|
|
}
|
|
|
|
process.BeginOutputReadLine();
|
|
process.BeginErrorReadLine();
|
|
await process.WaitForExitAsync().ConfigureAwait(true);
|
|
|
|
var summary = new StringBuilder();
|
|
summary.AppendLine($"Exit code: {process.ExitCode}");
|
|
|
|
if (outputLines.Length > 0)
|
|
{
|
|
summary.AppendLine();
|
|
summary.AppendLine("STDOUT:");
|
|
summary.Append(outputLines);
|
|
}
|
|
|
|
if (errorLines.Length > 0)
|
|
{
|
|
summary.AppendLine();
|
|
summary.AppendLine("STDERR:");
|
|
summary.Append(errorLines);
|
|
}
|
|
|
|
outputLogBox.Text = summary.ToString();
|
|
statusBlock.Text = process.ExitCode == 0
|
|
? "Face encoder completato."
|
|
: $"Face encoder terminato con errore (code {process.ExitCode}).";
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Face encoder execution failed.");
|
|
outputLogBox.Text = ex.ToString();
|
|
statusBlock.Text = "Errore durante esecuzione face encoder.";
|
|
}
|
|
finally
|
|
{
|
|
runButton.IsEnabled = true;
|
|
}
|
|
}
|
|
|
|
private static void OpenInExplorer(string? path)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(path))
|
|
{
|
|
return;
|
|
}
|
|
|
|
var normalizedPath = path.Trim().Trim('"');
|
|
try
|
|
{
|
|
if (File.Exists(normalizedPath))
|
|
{
|
|
Process.Start("explorer.exe", $"/select,\"{normalizedPath}\"");
|
|
}
|
|
else if (Directory.Exists(normalizedPath))
|
|
{
|
|
Process.Start(new ProcessStartInfo { FileName = normalizedPath, UseShellExecute = true });
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// Ignore failures when opening Explorer.
|
|
}
|
|
}
|
|
|
|
private static string NormalizeDirectoryPathArgument(string value)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(value))
|
|
{
|
|
return string.Empty;
|
|
}
|
|
|
|
var normalized = value.Trim().Trim('"');
|
|
var root = Path.GetPathRoot(normalized);
|
|
if (!string.IsNullOrEmpty(root) && normalized.Length > root.Length)
|
|
{
|
|
normalized = normalized.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
|
|
}
|
|
|
|
return normalized;
|
|
}
|
|
}
|