using ShellProgressBar; using System.Text.Json; using View_by_Distance.FaceRecognitionDotNet; using View_by_Distance.Map.Models; using View_by_Distance.Shared.Models; using View_by_Distance.Shared.Models.Properties; namespace View_by_Distance.Distance.Models; public class E_Distance : Shared.Models.Methods.IFaceDistance { private readonly Serilog.ILogger? _Log; private readonly string _ResultAllInOne; private readonly int _FaceDistancePermyriad; private readonly double _FaceDistanceTolerance; private readonly int _SortingDaysDeltaTolerance; private readonly bool _DistanceMoveUnableToMatch; private readonly int _DistancePixelDistanceTolerance; private readonly double _FaceDistanceMinimumConfidence; private readonly int _SortingMaximumPerFaceShouldBeHigh; public E_Distance(bool distanceMoveUnableToMatch, int distancePixelDistanceTolerance, double faceDistanceMinimumConfidence, int faceDistancePermyriad, double faceDistanceTolerance, string resultAllInOne, int sortingDaysDeltaTolerance, int sortingMaximumPerFaceShouldBeHigh) { _ResultAllInOne = resultAllInOne; _Log = Serilog.Log.ForContext(); _FaceDistancePermyriad = faceDistancePermyriad; _FaceDistanceTolerance = faceDistanceTolerance; _DistanceMoveUnableToMatch = distanceMoveUnableToMatch; _SortingDaysDeltaTolerance = sortingDaysDeltaTolerance; _FaceDistanceMinimumConfidence = faceDistanceMinimumConfidence; _DistancePixelDistanceTolerance = distancePixelDistanceTolerance; _SortingMaximumPerFaceShouldBeHigh = sortingMaximumPerFaceShouldBeHigh; } public static void SaveFaceDistances(Property.Models.Configuration configuration, SortingContainer[] sortingContainers) { string eDistanceContentCollectionDirectory = Property.Models.Stateless.IResult.GetResultsDateGroupDirectory(configuration, nameof(E_Distance), "([])"); if (!Directory.Exists(eDistanceContentCollectionDirectory)) _ = Directory.CreateDirectory(eDistanceContentCollectionDirectory); #pragma warning disable string[] results = (from l in sortingContainers select string.Concat(l.Sorting.WithinRange, '\t', l.Sorting.DistancePermyriad, '\t', l.Sorting.DaysDelta, '\t', l.Sorting.Id, '\t', l.Sorting.NormalizedPixelPercentage, '\t', l.Sorting.Older, '\t', l.Face.Mapping.MappingFromItem.Id, '\t', l.Face.Mapping.MappingFromLocation.NormalizedPixelPercentage)).ToArray(); #pragma warning restore string eDistanceContentFileName = Path.Combine(eDistanceContentCollectionDirectory, $"{configuration.ResultAllInOne}.tvs"); File.WriteAllLines(eDistanceContentFileName, results); } private List GetSortingCollection(MapLogic mapLogic, List faceDistanceEncodings, int faceDistanceContainersLength, int i, FaceDistance faceDistanceEncoding) { List results; List faceDistanceLengths = FaceRecognition.FaceDistances(faceDistanceEncodings, faceDistanceEncoding); if (faceDistanceLengths.Count != faceDistanceContainersLength) throw new NotSupportedException(); bool anyLowerThanTolerance = (from l in faceDistanceLengths where l.Length is not null && l.Length.Value != 0 && l.Length.Value < _FaceDistanceTolerance select true).Any(); results = mapLogic.GetSortingCollection(i, faceDistanceEncoding, faceDistanceLengths, anyLowerThanTolerance); return results; } private List GetSortingContainers(Face face, FaceDistance faceDistanceEncoding, List sortingCollection) { List results = new(); SortingContainer sortingContainer; Sorting[] collection = Shared.Models.Stateless.Methods.ISorting.Sort(sortingCollection); foreach (Sorting sorting in collection) { if (face.Mapping is null || faceDistanceEncoding.NormalizedPixelPercentage is null) throw new NotSupportedException(); if (face.Mapping.MappingFromLocation.Confidence < _FaceDistanceMinimumConfidence || sorting.DistancePermyriad > _FaceDistancePermyriad || sorting.DaysDelta > _SortingDaysDeltaTolerance) continue; sortingContainer = new(face, sorting); results.Add(sortingContainer); if (results.Count >= _SortingMaximumPerFaceShouldBeHigh) break; } return results; } private static FaceDistanceContainer[] GetOrderedFaceDistanceContainers(List collection) { FaceDistanceContainer[] results; results = (from l in collection orderby l.FaceDistance.Encoding is not null select l).ToArray(); if (results.Any() && results[0].FaceDistance.Encoding is null) throw new Exception("Sorting failed!"); return results; } private static FaceDistanceContainer[] GetOrderedFaceDistanceContainers(Face[] selectedFilteredFaces) { FaceDistanceContainer[] results; FaceDistance faceDistance; FaceDistanceContainer faceDistanceContainer; List collection = new(); foreach (Face face in selectedFilteredFaces) { if (face.Mapping is null) throw new NotSupportedException(); if (face.FaceDistance?.Encoding is not FaceRecognitionDotNet.FaceEncoding faceEncoding) continue; faceDistance = new(face.Mapping.MappingFromLocation.Confidence, faceEncoding, face.Mapping.MappingFromItem.Id, face.Mapping.MappingFromItem.IsWrongYear, face.Mapping.MappingFromItem.MinimumDateTime, face.Mapping.MappingFromLocation.NormalizedPixelPercentage); faceDistanceContainer = new(face, faceDistance); collection.Add(faceDistanceContainer); } results = GetOrderedFaceDistanceContainers(collection); return results; } private static List GetFaceDistanceEncodings(FaceDistanceContainer[] faceDistanceContainers) { List faceDistanceEncodings = new(); foreach (FaceDistanceContainer faceDistanceContainer in faceDistanceContainers) { if (faceDistanceContainer.FaceDistance.Encoding is null) continue; faceDistanceEncodings.Add(faceDistanceContainer.FaceDistance); } return faceDistanceEncodings; } public SortingContainer[] SetFaceMappingSortingCollectionThenGetSortingContainers(int maxDegreeOfParallelism, long ticks, MapLogic mapLogic, Face[] selectedFilteredFaces) { SortingContainer[] results; List collection = new(); int totalSeconds = (int)Math.Floor(new TimeSpan(DateTime.Now.Ticks - ticks).TotalSeconds); ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = maxDegreeOfParallelism }; FaceDistanceContainer[] faceDistanceContainers = GetOrderedFaceDistanceContainers(selectedFilteredFaces); List faceDistanceEncodings = GetFaceDistanceEncodings(faceDistanceContainers); string message = $") {faceDistanceContainers.Length:000} Get Sorting Containers Then Set Face Mapping Sorting Collection - {totalSeconds} total second(s)"; ProgressBarOptions options = new() { ProgressCharacter = '─', ProgressBarOnBottom = true, DisableBottomPercentage = true }; foreach (Face face in selectedFilteredFaces) face.ClearFaceDistance(); using ProgressBar progressBar = new(faceDistanceContainers.Length, message, options); _ = Parallel.For(0, faceDistanceContainers.Length, parallelOptions, (i, state) => { progressBar.Tick(); Face face = faceDistanceContainers[i].Face; if (face.Mapping is null) throw new NotSupportedException(); FaceDistance faceDistanceEncoding = faceDistanceContainers[i].FaceDistance; List sortingCollection = GetSortingCollection(mapLogic, faceDistanceEncodings, faceDistanceContainers.Length, i, faceDistanceEncoding); List sortingContainers = GetSortingContainers(face, faceDistanceEncoding, sortingCollection); lock (collection) collection.AddRange(sortingContainers); lock (face) face.ReleaseFaceDistance(); }); results = Shared.Models.Stateless.Methods.ISortingContainer.Sort(collection); return results; } public static Face[] GetSelectedFilteredFaces(List distinctFilteredFaces) { Face[] results = (from l in distinctFilteredFaces orderby l.Mapping is not null, l.Mapping?.MappingFromItem.MinimumDateTime descending select l).ToArray(); return results; } public static void SetFaceDistances(int maxDegreeOfParallelism, long ticks, Face[] selectedFilteredFaces) { int totalSeconds = (int)Math.Floor(new TimeSpan(DateTime.Now.Ticks - ticks).TotalSeconds); ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = maxDegreeOfParallelism }; string message = $") {selectedFilteredFaces.Length:000} Load Face Encoding - {totalSeconds} total second(s)"; ProgressBarOptions options = new() { ProgressCharacter = '─', ProgressBarOnBottom = true, DisableBottomPercentage = true }; using ProgressBar progressBar = new(selectedFilteredFaces.Length, message, options); _ = Parallel.For(0, selectedFilteredFaces.Length, parallelOptions, (i, state) => { Face face = selectedFilteredFaces[i]; if (face.FaceEncoding is null || face.Mapping is null) throw new NotSupportedException(); if (face.FaceDistance?.Encoding is not null && face.FaceDistance.Encoding is FaceRecognitionDotNet.FaceEncoding faceEncoding) return; progressBar.Tick(); faceEncoding = FaceRecognition.LoadFaceEncoding(face.FaceEncoding.RawEncoding); FaceDistance faceDistance = new(face.Mapping.MappingFromLocation.Confidence, faceEncoding, face.Mapping.MappingFromItem.Id, face.Mapping.MappingFromItem.IsWrongYear, face.Mapping.MappingFromItem.MinimumDateTime, face.Mapping.MappingFromLocation.NormalizedPixelPercentage); lock (face) face.SetFaceDistance(faceDistance); }); } void Shared.Models.Methods.IFaceDistance.SavePossiblyNewPersonContainers(IPropertyConfiguration propertyConfiguration, string personBirthdayFormat, string facesFileNameExtension, string a2PeopleSingletonDirectory, Dictionary personKeyToPersonContainer, List<(string[], PersonContainer)> possiblyNewPersonDisplayDirectoryNamesAndPersonContainer) { char @char; string json; string[] files; string checkFile; string[] segments; const int zero = 0; string personKeyFormatted; string personDisplayDirectory; PersonBirthday personBirthday; string personDisplayDirectoryName; string checkPersonDisplayDirectory; string checkPersonKeyFormattedDirectory; char[] chars = Shared.Models.Stateless.Methods.IAge.GetChars(); JsonSerializerOptions jsonSerializerOptions = new() { WriteIndented = true }; foreach ((string[] personDisplayDirectoryNames, PersonContainer personContainer) in possiblyNewPersonDisplayDirectoryNamesAndPersonContainer) { if (personContainer.Key is null || personContainer.Birthdays is null || !personContainer.Birthdays.Any()) continue; personBirthday = personContainer.Birthdays[zero]; personDisplayDirectoryName = personDisplayDirectoryNames[^1]; personDisplayDirectory = Path.Combine(personDisplayDirectoryNames); personKeyFormatted = Shared.Models.Stateless.Methods.IPersonBirthday.GetFormatted(personBirthdayFormat, personBirthday); segments = personDisplayDirectoryName.Split(chars); if (segments.Length != 2) @char = '_'; else @char = personDisplayDirectoryName[segments[zero].Length]; checkPersonDisplayDirectory = Path.Combine(a2PeopleSingletonDirectory, @char.ToString(), personDisplayDirectoryName); checkPersonKeyFormattedDirectory = Path.Combine(checkPersonDisplayDirectory, personKeyFormatted); if (Directory.Exists(checkPersonKeyFormattedDirectory)) continue; _ = Directory.CreateDirectory(checkPersonKeyFormattedDirectory); checkFile = Path.Combine(checkPersonKeyFormattedDirectory, $"{personKeyFormatted}.json"); json = JsonSerializer.Serialize(personContainer.Person, jsonSerializerOptions); _ = Shared.Models.Stateless.Methods.IPath.WriteAllText(checkFile, json, updateDateWhenMatches: false, compareBeforeWrite: true); if (!Directory.Exists(personDisplayDirectory)) continue; files = Directory.GetFiles(personDisplayDirectory, $"*{facesFileNameExtension}", SearchOption.TopDirectoryOnly); foreach (string file in files) { checkFile = Path.Combine(checkPersonDisplayDirectory, Path.GetFileName(file)); if (File.Exists(checkFile)) continue; File.Copy(files[0], checkFile); break; } } } private void MoveUnableToMatch(string eDistanceContentDirectory, string file) { bool result; string? fileName = Path.GetFileName(file); string? directoryName = Path.GetDirectoryName(file); if (fileName is null || directoryName is null) result = false; else { if (string.IsNullOrEmpty(directoryName) || string.IsNullOrEmpty(directoryName) || !directoryName.Contains(eDistanceContentDirectory)) result = false; else { List directoryNames = new(); string? checkDirectoryName = directoryName; for (int i = 0; i < int.MaxValue; i++) { if (string.IsNullOrEmpty(checkDirectoryName)) continue; directoryNames.Add(Path.GetFileName(checkDirectoryName)); checkDirectoryName = Path.GetDirectoryName(checkDirectoryName); if (string.IsNullOrEmpty(checkDirectoryName)) continue; if (checkDirectoryName == eDistanceContentDirectory) break; } if (string.IsNullOrEmpty(checkDirectoryName) || !directoryNames.Any() || !long.TryParse(directoryNames[^1][1..^1], out long directoryTicks)) { result = false; File.Delete(file); } else { checkDirectoryName = Path.Combine(checkDirectoryName, $"({directoryTicks}{_FaceDistanceTolerance.ToString()[1..]})"); for (int i = directoryNames.Count - 1 - 1; i > -1; i--) checkDirectoryName = Path.Combine(checkDirectoryName, directoryNames[i]); if (!Directory.Exists(checkDirectoryName)) _ = Directory.CreateDirectory(checkDirectoryName); File.Move(file, Path.Combine(checkDirectoryName, fileName)); result = true; } } } if (result) { } } private static string[] GetMatchingDuplicates(string[] mappedFaceFiles, List duplicateMappedFaceFiles, string mappedFaceFile) { string[] results; string checkFile; FileInfo fileInfo = new(mappedFaceFile); List<(long Length, string FullName)> collection = new(); if (fileInfo.Exists) collection.Add(new(fileInfo.Length, fileInfo.FullName)); string fileName = Path.GetFileName(mappedFaceFile); foreach (string file in mappedFaceFiles) { if (duplicateMappedFaceFiles.Contains(file)) continue; if (file == mappedFaceFile || !file.EndsWith(fileName)) continue; fileInfo = new(file); if (!fileInfo.Exists) continue; collection.Add(new(fileInfo.Length, fileInfo.FullName)); } collection = collection.OrderBy(l => l.Length).ToList(); for (int i = 0; i < collection.Count - 1; i++) { checkFile = string.Concat(collection[i].FullName, ".dup"); if (File.Exists(checkFile)) continue; File.Move(collection[i].FullName, checkFile); } results = (from l in collection select l.FullName).ToArray(); return results; } public static string? GetFaceEncoding(string file) { string? result; List results = new(); const string comment = "Comment: "; if (File.Exists(file)) { IReadOnlyList directories = MetadataExtractor.ImageMetadataReader.ReadMetadata(file); foreach (MetadataExtractor.Directory directory in directories) { if (directory.Name != "PNG-tEXt") continue; foreach (MetadataExtractor.Tag tag in directory.Tags) { if (tag.Name != "Textual Data" || string.IsNullOrEmpty(tag.Description)) continue; if (!tag.Description.StartsWith(comment)) continue; results.Add(tag.Description); } } } result = results.Any() ? results[0][comment.Length..] : null; return result; } private static FaceDistanceContainer[] GetOrderedFaceDistanceContainers(MappingFromItem mappingFromItem, List faces) { FaceDistanceContainer[] results; FaceDistance faceDistance; int normalizedPixelPercentage; FaceDistanceContainer faceDistanceContainer; List collection = new(); foreach (Face face in faces) { if (face.FaceEncoding is null || face.Location is null || face.OutputResolution is null) throw new NotSupportedException(); normalizedPixelPercentage = Shared.Models.Stateless.Methods.ILocation.GetNormalizedPixelPercentage(face.Location, Shared.Models.Stateless.ILocation.Digits, Shared.Models.Stateless.ILocation.Factor, face.OutputResolution); if (face.FaceDistance?.Encoding is not null && face.FaceDistance.Encoding is FaceRecognitionDotNet.FaceEncoding faceEncoding) faceDistance = new(face.Location.Confidence, faceEncoding, mappingFromItem.Id, mappingFromItem.IsWrongYear, mappingFromItem.MinimumDateTime, normalizedPixelPercentage); else { faceEncoding = FaceRecognition.LoadFaceEncoding(face.FaceEncoding.RawEncoding); faceDistance = new(face.Location.Confidence, faceEncoding, mappingFromItem.Id, mappingFromItem.IsWrongYear, mappingFromItem.MinimumDateTime, normalizedPixelPercentage); lock (faces) face.SetFaceDistance(faceDistance); } faceDistanceContainer = new(face, faceDistance); collection.Add(faceDistanceContainer); } results = GetOrderedFaceDistanceContainers(collection); return results; } private static List<(Face Face, double Length)> GetValues(double faceDistanceTolerance, MappingFromItem mappingFromItem, List faces, string json) { List<(Face Face, double Length)> results = new(); Face face; FaceDistance faceDistanceLength; Shared.Models.FaceEncoding? modelsFaceEncoding = JsonSerializer.Deserialize(json); if (modelsFaceEncoding is null) throw new NotSupportedException(); FaceRecognitionDotNet.FaceEncoding faceRecognitionDotNetFaceEncoding = FaceRecognition.LoadFaceEncoding(modelsFaceEncoding.RawEncoding); FaceDistance faceDistanceEncoding = new(faceRecognitionDotNetFaceEncoding); FaceDistanceContainer[] faceDistanceContainers = GetOrderedFaceDistanceContainers(mappingFromItem, faces); int faceDistanceContainersLength = faceDistanceContainers.Length; if (faceDistanceContainersLength != faces.Count) throw new NotSupportedException(); List faceDistanceEncodings = GetFaceDistanceEncodings(faceDistanceContainers); if (faceDistanceEncodings.Count != faces.Count) throw new NotSupportedException(); List faceDistanceLengths = FaceRecognition.FaceDistances(faceDistanceEncodings, faceDistanceEncoding); if (faceDistanceLengths.Count != faceDistanceContainersLength) throw new NotSupportedException(); for (int i = 0; i < faces.Count; i++) { face = faces[i]; faceDistanceLength = faceDistanceLengths[i]; if (faceDistanceLength.Length is null) throw new NotSupportedException(); if (faceDistanceLength.Length.Value > faceDistanceTolerance) continue; results.Add(new(face, faceDistanceLength.Length.Value)); } return results; } private static Face[] GetMatchingFaces(double faceDistanceTolerance, MappingFromItem mappingFromItem, List faces, string json) { Face[] results; List<(Face Face, double Length)> collection = GetValues(faceDistanceTolerance, mappingFromItem, faces, json); if (!collection.Any()) results = Array.Empty(); else results = (from l in collection orderby l.Length select l.Face).Take(1).ToArray(); return results; } private static Face[] GetMatchingFaces(int pixelDistanceTolerance, List faces) { Face[] results; int? x; int? y; double distance; double center = 2f; double xCenterValue; double yCenterValue; int normalizedPixelPercentage; string normalizedPixelPercentagePadded; List<(double Order, Face Face)> collection = new(); foreach (Face face in faces) { if (face.Location is null || face.OutputResolution is null) throw new NotSupportedException(); xCenterValue = (face.Location.Left + face.Location.Right) / center; yCenterValue = (face.Location.Top + face.Location.Bottom) / center; if (xCenterValue < face.Location.Left || xCenterValue > face.Location.Right) throw new Exception(); if (yCenterValue < face.Location.Top || yCenterValue > face.Location.Bottom) throw new Exception(); normalizedPixelPercentage = Shared.Models.Stateless.Methods.ILocation.GetNormalizedPixelPercentage(face.Location, Shared.Models.Stateless.ILocation.Digits, Shared.Models.Stateless.ILocation.Factor, face.OutputResolution); normalizedPixelPercentagePadded = Shared.Models.Stateless.Methods.ILocation.GetLeftPadded(Shared.Models.Stateless.ILocation.Digits, normalizedPixelPercentage); (x, y) = Shared.Models.Stateless.Methods.ILocation.GetXY(Shared.Models.Stateless.ILocation.Digits, Shared.Models.Stateless.ILocation.Factor, face.OutputResolution.Width, face.OutputResolution.Height, normalizedPixelPercentagePadded); if (x is null || y is null) throw new NotSupportedException(); distance = Math.Sqrt(Math.Pow(xCenterValue - x.Value, 2) + Math.Pow(yCenterValue - y.Value, 2)); collection.Add(new(distance, face)); } if (!collection.Any()) results = Array.Empty(); else results = (from l in collection orderby l.Order where l.Order < pixelDistanceTolerance select l.Face).Take(1).ToArray(); return results; } private static List GetMatchingFaces(List faces, string? json) { List results = new(); string check; foreach (Face face in faces) { if (json is null || face.FaceEncoding is null) continue; if (!json.Contains(face.FaceEncoding.RawEncoding[0].ToString())) continue; check = JsonSerializer.Serialize(face.FaceEncoding); if (check != json) continue; results.Add(face); } return results; } private static FileInfo? CheckFileThenGetFileInfo(string facesFileNameExtension, Item item, string mappedFaceDirectory, string mappedFaceFile, List checkFaces) { FileInfo? result = null; string checkFile; string deterministicHashCodeKey; foreach (Face face in checkFaces) { if (checkFaces.Count != 1) break; if (item.Property?.Id is null || item.ImageFileHolder is null) throw new NotSupportedException(); if (face.FaceEncoding is null || face.Location is null || face.OutputResolution is null) throw new NotSupportedException(); deterministicHashCodeKey = Shared.Models.Stateless.Methods.IMapping.GetDeterministicHashCodeKey(item.Property.Id.Value, face.Location, Shared.Models.Stateless.ILocation.Digits, Shared.Models.Stateless.ILocation.Factor, face.OutputResolution); checkFile = Path.Combine(mappedFaceDirectory, $"{deterministicHashCodeKey}{item.ImageFileHolder.ExtensionLowered}{facesFileNameExtension}"); if (checkFile == mappedFaceFile) continue; result = new FileInfo(checkFile); if (!result.Exists) continue; File.Delete(result.FullName); result = null; } return result; } private static List GetMatchingFaces(List faces, string[] mappedFaceFiles, List debugChecks, int normalizedPixelPercentageValue, List normalizedPixelPercentages, List duplicateMappedFaceFiles, string mappedFaceFile) { List results = new(); bool check; int normalizedPixelPercentage; foreach (Face face in faces) { if (face.Location is null || face.OutputResolution is null) throw new NotSupportedException(); normalizedPixelPercentage = Shared.Models.Stateless.Methods.ILocation.GetNormalizedPixelPercentage(face.Location, Shared.Models.Stateless.ILocation.Digits, Shared.Models.Stateless.ILocation.Factor, face.OutputResolution); debugChecks.Add(normalizedPixelPercentage); if (normalizedPixelPercentage != normalizedPixelPercentageValue) continue; if (normalizedPixelPercentages.Contains(normalizedPixelPercentage)) { duplicateMappedFaceFiles.AddRange(GetMatchingDuplicates(mappedFaceFiles, duplicateMappedFaceFiles, mappedFaceFile)); continue; } check = true; results.Add(face); if (!check) debugChecks.Add(normalizedPixelPercentage); } return results; } public (int, int, int) GetUnableToMatchCountAndRenameMatches(string facesFileNameExtension, string eDistanceContentDirectory, Item item, List faces, string[] mappedFaceFiles) { int result = 0; int? id; string? json; int renamed = 0; bool? isWrongYear; FileInfo? fileInfo; DateTime minimumDateTime; string? mappedFaceDirectory; List checkFaces = new(); List debugChecks = new(); int? normalizedPixelPercentage; MappingFromItem mappingFromItem; List normalizedPixelPercentages; List duplicateMappedFaceFiles = new(); Dictionary> idToNormalizedPixelPercentages = new(); foreach (string mappedFaceFile in mappedFaceFiles) { mappedFaceDirectory = Path.GetDirectoryName(mappedFaceFile); if (mappedFaceDirectory is null) throw new NotSupportedException(); if (item.Property?.Id is null) throw new NotSupportedException(); if (duplicateMappedFaceFiles.Contains(mappedFaceFile)) continue; (id, normalizedPixelPercentage, _) = Shared.Models.Stateless.Methods.IMapping.GetReversedDeterministicHashCodeKey(facesFileNameExtension, mappedFaceFile); if (id is null || normalizedPixelPercentage is null) { result++; continue; } if (id.Value != item.Property.Id.Value) continue; if (item.Property?.Id is null || item.ImageFileHolder is null || item.ResizedFileHolder is null) continue; if (faces.Any(l => l.FaceEncoding is null || l.Location is null || l.OutputResolution is null)) continue; minimumDateTime = Shared.Models.Stateless.Methods.IProperty.GetMinimumDateTime(item.Property); (isWrongYear, _) = item.Property.IsWrongYear(item.ImageFileHolder.FullName, minimumDateTime); mappingFromItem = new(item.Property.Id.Value, item.ImageFileHolder, isWrongYear, minimumDateTime, item.ResizedFileHolder); json = null; checkFaces.Clear(); debugChecks.Clear(); if (!idToNormalizedPixelPercentages.ContainsKey(id.Value)) idToNormalizedPixelPercentages.Add(id.Value, new()); normalizedPixelPercentages = idToNormalizedPixelPercentages[id.Value]; checkFaces.AddRange(GetMatchingFaces(faces, mappedFaceFiles, debugChecks, normalizedPixelPercentage.Value, normalizedPixelPercentages, duplicateMappedFaceFiles, mappedFaceFile)); if (checkFaces.Count != 1) { checkFaces.Clear(); json = GetFaceEncoding(mappedFaceFile); if (json is null) { result++; if (_DistanceMoveUnableToMatch) MoveUnableToMatch(eDistanceContentDirectory, mappedFaceFile); continue; } checkFaces.AddRange(GetMatchingFaces(faces, json)); } if (checkFaces.Count != 1 && !string.IsNullOrEmpty(json)) { checkFaces.Clear(); if (json is null) throw new NotSupportedException(); checkFaces.AddRange(GetMatchingFaces(_FaceDistanceTolerance, mappingFromItem, faces, json)); } if (checkFaces.Count != 1 && _DistancePixelDistanceTolerance > 0) { checkFaces.Clear(); checkFaces.AddRange(GetMatchingFaces(_DistancePixelDistanceTolerance, faces)); } if (!checkFaces.Any() && faces.Count == 1) checkFaces.AddRange(faces); if (!checkFaces.Any()) { result++; if (_DistanceMoveUnableToMatch) MoveUnableToMatch(eDistanceContentDirectory, mappedFaceFile); continue; } if (checkFaces.Count != 1) { result++; if (_DistanceMoveUnableToMatch) MoveUnableToMatch(eDistanceContentDirectory, mappedFaceFile); continue; } normalizedPixelPercentages.Add(normalizedPixelPercentage.Value); fileInfo = CheckFileThenGetFileInfo(facesFileNameExtension, item, mappedFaceDirectory, mappedFaceFile, checkFaces); if (fileInfo is null) continue; File.Move(mappedFaceFile, fileInfo.FullName); renamed++; } if (duplicateMappedFaceFiles.Any()) { duplicateMappedFaceFiles.Sort(); } return new(result, duplicateMappedFaceFiles.Count, renamed); } }