129 lines
4.7 KiB
C#
129 lines
4.7 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using AIFotoONLUS.Core;
|
|
using ImageCatalog_2.Models;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace ImageCatalog_2.Services;
|
|
|
|
public class AiExtractionService : IAiExtractionService
|
|
{
|
|
private readonly ILogger<AiExtractionService> _logger;
|
|
|
|
public AiExtractionService(ILogger<AiExtractionService> logger)
|
|
{
|
|
_logger = logger;
|
|
}
|
|
|
|
public async Task RunAsync(
|
|
AiExtractionRequest request,
|
|
CancellationToken token,
|
|
Func<AiResultItem, Task> onResult,
|
|
Func<double, Task> onProgress)
|
|
{
|
|
var searchOption = request.Recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
|
|
|
|
var imageFiles = Directory.EnumerateFiles(request.SearchRoot, "*.*", searchOption)
|
|
.Where(f => f.EndsWith(".jpg", StringComparison.OrdinalIgnoreCase)
|
|
|| f.EndsWith(".jpeg", StringComparison.OrdinalIgnoreCase)
|
|
|| f.EndsWith(".png", StringComparison.OrdinalIgnoreCase)
|
|
|| f.EndsWith(".bmp", StringComparison.OrdinalIgnoreCase)
|
|
|| f.EndsWith(".gif", StringComparison.OrdinalIgnoreCase))
|
|
.Where(f => request.IncludeThumbnails || !Path.GetFileName(f).StartsWith("tn_", StringComparison.OrdinalIgnoreCase))
|
|
.ToList();
|
|
|
|
var extractedResults = new List<AiResultItem>();
|
|
var modelConfiguration = BuildModelConfiguration(request.ModelsFolderPath, request.UseGpu);
|
|
|
|
using var engine = new NumberRecognitionEngine(modelConfiguration, _logger);
|
|
|
|
var processed = 0;
|
|
var total = imageFiles.Count;
|
|
var failed = 0;
|
|
Exception? firstFailure = null;
|
|
|
|
foreach (var file in imageFiles)
|
|
{
|
|
token.ThrowIfCancellationRequested();
|
|
|
|
var extracted = string.Empty;
|
|
|
|
try
|
|
{
|
|
extracted = engine.ProcessImage(file).Text;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
failed++;
|
|
firstFailure ??= ex;
|
|
_logger.LogWarning(ex, "Error processing AI OCR for {File}", file);
|
|
}
|
|
|
|
var result = new AiResultItem { Path = file, Text = extracted };
|
|
extractedResults.Add(result);
|
|
await onResult(result).ConfigureAwait(false);
|
|
|
|
processed++;
|
|
var percent = total > 0 ? (processed * 100.0 / total) : 100.0;
|
|
await onProgress(percent).ConfigureAwait(false);
|
|
}
|
|
|
|
if (imageFiles.Count > 0 && failed == imageFiles.Count)
|
|
{
|
|
throw new InvalidOperationException($"AI OCR failed for all {imageFiles.Count} image(s). See previous log entries for details.", firstFailure);
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(request.CsvOutputPath))
|
|
{
|
|
try
|
|
{
|
|
var dir = Path.GetDirectoryName(request.CsvOutputPath) ?? string.Empty;
|
|
if (!string.IsNullOrWhiteSpace(dir) && !Directory.Exists(dir))
|
|
{
|
|
Directory.CreateDirectory(dir);
|
|
}
|
|
|
|
using var sw = new StreamWriter(request.CsvOutputPath, false, Encoding.UTF8);
|
|
sw.WriteLine("Path,Text");
|
|
foreach (var r in extractedResults)
|
|
{
|
|
var csvFileName = Path.GetFileName(r.Path ?? string.Empty);
|
|
var safeText = (r.Text ?? string.Empty).Replace("\"", "\"\"");
|
|
sw.WriteLine($"\"{csvFileName}\",\"{safeText}\"");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Failed to write CSV to {CsvOutputPath}", request.CsvOutputPath);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static ModelConfiguration BuildModelConfiguration(string modelsFolderPath, bool useGpu)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(modelsFolderPath))
|
|
{
|
|
throw new InvalidOperationException("AI models folder is not configured.");
|
|
}
|
|
|
|
var modelsRoot = Path.GetFullPath(modelsFolderPath.Trim().Trim('"'));
|
|
if (!Directory.Exists(modelsRoot))
|
|
{
|
|
throw new DirectoryNotFoundException($"AI models folder not found: {modelsRoot}");
|
|
}
|
|
|
|
return new ModelConfiguration
|
|
{
|
|
DetectionCfg = Path.Combine(modelsRoot, "detection.cfg"),
|
|
DetectionWeights = Path.Combine(modelsRoot, "detection.weights"),
|
|
RecognitionCfg = Path.Combine(modelsRoot, "recognition.cfg"),
|
|
RecognitionWeights = Path.Combine(modelsRoot, "recognition.weights"),
|
|
UseGpu = useGpu
|
|
};
|
|
}
|
|
}
|