using ShellProgressBar; using System.Collections.ObjectModel; using System.Text.Json; using View_by_Distance.Distance.Models.Stateless; using View_by_Distance.FaceRecognitionDotNet; using View_by_Distance.Property.Models.Stateless; using View_by_Distance.Shared.Models; using View_by_Distance.Shared.Models.Methods; namespace View_by_Distance.Distance.Models; public partial class E_Distance : IDistance { internal record Mapped(string File, double? Length, string Link, string Image); internal record Record(string File, FaceRecognitionDotNet.FaceEncoding FaceRecognitionDotNetFaceEncoding); private readonly List _Moved; private readonly List _Debug; private readonly List _Renamed; private readonly Serilog.ILogger? _Log; private readonly int _FaceConfidencePercent; private readonly bool _DistanceRenameToMatch; private readonly bool _DistanceMoveUnableToMatch; private readonly float _RectangleIntersectMinimum; private readonly List _AllMappedFaceFiles; private readonly List _AllMappedFaceFileNames; private readonly double _RangeDistanceToleranceAverage; private readonly List _DuplicateMappedFaceFiles; public E_Distance(bool distanceMoveUnableToMatch, bool distanceRenameToMatch, int faceConfidencePercent, float[] rangeDistanceTolerance, float[] rectangleIntersectMinimums) { _Debug = new(); _Moved = new(); _Renamed = new(); _AllMappedFaceFiles = new(); _AllMappedFaceFileNames = new(); _DuplicateMappedFaceFiles = new(); _Log = Serilog.Log.ForContext(); _DistanceRenameToMatch = distanceRenameToMatch; _FaceConfidencePercent = faceConfidencePercent; _DistanceMoveUnableToMatch = distanceMoveUnableToMatch; _RectangleIntersectMinimum = rectangleIntersectMinimums.Max(); _RangeDistanceToleranceAverage = rangeDistanceTolerance.Average(); } private static void MoveUnableToMatch(string file) { string checkFile = $"{file}.unk"; if (File.Exists(file) && !File.Exists(checkFile)) File.Move(file, checkFile); } private FaceDistanceContainer[] GetFaceDistanceContainers(MappingFromItem mappingFromItem, List intersectFaces) { FaceDistanceContainer[] results; int wholePercentages; int confidencePercent; FaceDistance faceDistance; FaceDistanceContainer faceDistanceContainer; List collection = new(); foreach (Face face in intersectFaces) { if (face.FaceEncoding is null || face.Location is null || face.OutputResolution is null) throw new NotSupportedException(); if (face.Mapping?.MappingFromFilterPost is null) throw new NotSupportedException(); confidencePercent = Shared.Models.Stateless.Methods.ILocation.GetConfidencePercent(_FaceConfidencePercent, face.Location.Confidence); wholePercentages = Shared.Models.Stateless.Methods.ILocation.GetWholePercentages(face.Location, Shared.Models.Stateless.ILocation.Digits, face.OutputResolution); if (face.FaceDistance?.Encoding is not null && face.FaceDistance.Encoding is FaceRecognitionDotNet.FaceEncoding faceEncoding) faceDistance = new(confidencePercent, mappingFromItem.GetDateTimeOriginalThenMinimumDateTime(), faceEncoding, face.Mapping?.MappingFromFilterPost, mappingFromItem.Id, mappingFromItem.IsWrongYear, wholePercentages); else { faceEncoding = FaceRecognition.LoadFaceEncoding(face.FaceEncoding.RawEncoding); faceDistance = new(confidencePercent, mappingFromItem.GetDateTimeOriginalThenMinimumDateTime(), faceEncoding, face.Mapping?.MappingFromFilterPost, mappingFromItem.Id, mappingFromItem.IsWrongYear, wholePercentages); lock (intersectFaces) face.SetFaceDistance(faceDistance); } faceDistanceContainer = new(face, faceDistance); collection.Add(faceDistanceContainer); } results = collection.ToArray(); return results; } private static ReadOnlyCollection GetFaceDistanceEncodings(FaceDistanceContainer[] faceDistanceContainers) { List faceDistanceEncodings = new(); foreach (FaceDistanceContainer faceDistanceContainer in faceDistanceContainers) { if (faceDistanceContainer.FaceDistance.Encoding is null) continue; faceDistanceEncodings.Add(faceDistanceContainer.FaceDistance); } return new(faceDistanceEncodings); } private List<(Face Face, double? Length)> GetValues(MappingFromItem mappingFromItem, List intersectFaces, Shared.Models.FaceEncoding modelsFaceEncoding) { List<(Face Face, double? Length)> results = new(); Face face; FaceDistance faceDistanceLength; FaceRecognitionDotNet.FaceEncoding faceRecognitionDotNetFaceEncoding = FaceRecognition.LoadFaceEncoding(modelsFaceEncoding.RawEncoding); FaceDistance faceDistanceEncoding = new(faceRecognitionDotNetFaceEncoding); FaceDistanceContainer[] faceDistanceContainers = GetFaceDistanceContainers(mappingFromItem, intersectFaces); int faceDistanceContainersLength = faceDistanceContainers.Length; if (faceDistanceContainersLength != intersectFaces.Count) throw new NotSupportedException(); ReadOnlyCollection faceDistanceEncodings = GetFaceDistanceEncodings(faceDistanceContainers); if (faceDistanceEncodings.Count != intersectFaces.Count) throw new NotSupportedException(); List faceDistanceLengths = FaceRecognition.FaceDistances(faceDistanceEncodings, faceDistanceEncoding); if (faceDistanceLengths.Count != faceDistanceContainersLength) throw new NotSupportedException(); for (int i = 0; i < intersectFaces.Count; i++) { face = intersectFaces[i]; faceDistanceLength = faceDistanceLengths[i]; if (faceDistanceLength.Length is null) throw new NotSupportedException(); results.Add(new(face, faceDistanceLength.Length.Value)); } return results; } private (Face, double?)[] GetClosestFaceByDistanceIgnoringTolerance(MappingFromItem mappingFromItem, List intersectFaces, Shared.Models.FaceEncoding modelsFaceEncoding) { (Face, double?)[] results; List<(Face Face, double? Length)> collection = GetValues(mappingFromItem, intersectFaces, modelsFaceEncoding); results = (from l in collection where l.Length < _RangeDistanceToleranceAverage orderby l.Length select l).Take(1).ToArray(); if (results.Length > 0) { (Face _, double? length) = results.First(); _Debug.Add(length); } return results; } private static List<(Face, double?)> GetMatchingFacesByFaceEncoding(Face[] filteredFaces, string? json) { List<(Face, double?)> results = new(); string check; foreach (Face face in filteredFaces) { 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(new(face, 0)); } return results; } private static FileInfo? CheckFileThenGetFileInfo(string facesFileNameExtension, MappingFromItem mappingFromItem, string file, List<(Face, double?)> checkFaces) { FileInfo? result = null; string checkFile; string? mappedFaceDirectory; string deterministicHashCodeKey; foreach ((Face face, _) in checkFaces) { if (checkFaces.Count != 1) break; if (face.FaceEncoding is null || face.Location is null || face.OutputResolution is null) throw new NotSupportedException(); mappedFaceDirectory = Path.GetDirectoryName(file); if (mappedFaceDirectory is null) throw new NotSupportedException(); deterministicHashCodeKey = Shared.Models.Stateless.Methods.IMapping.GetDeterministicHashCodeKey(mappingFromItem.Id, face.Location, Shared.Models.Stateless.ILocation.Digits, face.OutputResolution); checkFile = Path.Combine(mappedFaceDirectory, $"{deterministicHashCodeKey}{mappingFromItem.ImageFileHolder.ExtensionLowered}{facesFileNameExtension}"); if (checkFile == file) continue; result = new FileInfo(checkFile); } return result; } private void AppendMatchingDuplicates(string file, string[] matches) { string checkFile; FileInfo fileInfo = new(file); List<(long Length, string FullName)> collection = new(); if (fileInfo.Exists) collection.Add(new(fileInfo.Length, fileInfo.FullName)); lock (_DuplicateMappedFaceFiles) _DuplicateMappedFaceFiles.Add(file); foreach (string match in matches) { fileInfo = new(match); if (!fileInfo.Exists) continue; collection.Add(new(fileInfo.Length, fileInfo.FullName)); break; } collection = collection.OrderBy(l => l.Length).ToList(); for (int i = 0; i < collection.Count - 1; i++) { checkFile = $"{collection[i].FullName}.dup"; if (File.Exists(checkFile)) continue; File.Move(collection[i].FullName, checkFile); } } public void LookForMatchFacesAndPossiblyRename(string facesFileNameExtension, MappingFromItem mappingFromItem, List faces, ReadOnlyCollection> locationContainers) { string? json; string fileName; string[] matches; FileInfo? fileInfo; List intersectFaces; List<(Face, double?)> checkFaces = new(); Shared.Models.FaceEncoding? modelsFaceEncoding; Face[] filteredFaces = (from l in faces where l.FaceEncoding is not null && l.Location is not null && l.OutputResolution is not null select l).ToArray(); if (filteredFaces.Length != faces.Count) checkFaces.Clear(); foreach (LocationContainer locationContainer in locationContainers) { if (_Renamed.Contains(locationContainer.File)) continue; fileName = Path.GetFileName(locationContainer.File); if (locationContainer.FromDistanceContent && _DuplicateMappedFaceFiles.Contains(fileName)) continue; checkFaces.Clear(); if (locationContainer.Directories.Count == 0) { if (locationContainer.FromDistanceContent) throw new NullReferenceException(nameof(locationContainer.Directories)); continue; } json = Metadata.Models.Stateless.Methods.IMetadata.GetFaceEncoding(locationContainer.Directories); if (json is null) { if (_DistanceMoveUnableToMatch) MoveUnableToMatch(locationContainer.File); continue; } if (filteredFaces.Length > 0) checkFaces.AddRange(GetMatchingFacesByFaceEncoding(filteredFaces, json)); if (checkFaces.Count == 1) _Debug.Add(0); if (checkFaces.Count != 1 && !string.IsNullOrEmpty(json)) { checkFaces.Clear(); modelsFaceEncoding = JsonSerializer.Deserialize(json); if (modelsFaceEncoding is null) throw new NotSupportedException(); if (filteredFaces.Length > 0) { intersectFaces = Shared.Models.Stateless.Methods.ILocation.FilterByIntersect(filteredFaces, _RectangleIntersectMinimum, locationContainer.WholePercentages); if (intersectFaces.Count > 0) checkFaces.AddRange(GetClosestFaceByDistanceIgnoringTolerance(mappingFromItem, intersectFaces, modelsFaceEncoding)); } } if (checkFaces.Count == 0) { if (_DistanceMoveUnableToMatch) MoveUnableToMatch(locationContainer.File); continue; } if (checkFaces.Count != 1) { if (_DistanceMoveUnableToMatch) MoveUnableToMatch(locationContainer.File); continue; } fileInfo = CheckFileThenGetFileInfo(facesFileNameExtension, mappingFromItem, locationContainer.File, checkFaces); if (fileInfo is not null) { if (_DistanceRenameToMatch && fileInfo is not null) { if (fileInfo.Exists) File.Delete(locationContainer.File); else File.Move(locationContainer.File, fileInfo.FullName); File.WriteAllText($"{fileInfo.FullName}.old", $"{fileInfo.FullName}{Environment.NewLine}{locationContainer.File}"); _Renamed.Add(locationContainer.File); } continue; } if (_AllMappedFaceFileNames.Contains(fileName)) { lock (_AllMappedFaceFiles) matches = (from l in _AllMappedFaceFiles where l != locationContainer.File && Path.GetFileName(l) == fileName select l).ToArray(); if (locationContainer.FromDistanceContent && matches.Length > 0) AppendMatchingDuplicates(locationContainer.File, matches); } if (!locationContainer.FromDistanceContent) continue; lock (_AllMappedFaceFiles) _AllMappedFaceFiles.Add(locationContainer.File); lock (_AllMappedFaceFileNames) _AllMappedFaceFileNames.Add(fileName); } } public void Clear() { if (_Log is null) throw new NullReferenceException(nameof(_Log)); double?[] debug = (from l in _Debug where l is null or not 0 select l).ToArray(); if (debug.Length > 0) { string debugMessage = $"{_Debug.Count - debug.Length} - {debug.Min()} - {_Debug.Max()}"; _Log.Info(debugMessage); } if (_DuplicateMappedFaceFiles.Count > 0) _Log.Info($"Renamed {_DuplicateMappedFaceFiles.Count} to *.dup file(s)"); if (_Moved.Count > 0 || _Renamed.Count > 0) throw new NotImplementedException("Restart!"); _Debug.Clear(); _Moved.Clear(); _Renamed.Clear(); _AllMappedFaceFiles.Clear(); _AllMappedFaceFileNames.Clear(); _DuplicateMappedFaceFiles.Clear(); } public static void SaveFaceDistances(Property.Models.Configuration configuration, SortingContainer[] sortingContainers) { string eDistanceContentCollectionDirectory = IResult.GetResultsDateGroupDirectory(configuration, nameof(E_Distance), "([])"); if (!Directory.Exists(eDistanceContentCollectionDirectory)) _ = Directory.CreateDirectory(eDistanceContentCollectionDirectory); #pragma warning disable string[] results = (from l in sortingContainers select l.ToString()).ToArray(); #pragma warning restore string eDistanceContentFileName = Path.Combine(eDistanceContentCollectionDirectory, $"{configuration.ResultAllInOne}.tvs"); File.WriteAllLines(eDistanceContentFileName, results); } public static void PreFilterSetFaceDistances(int maxDegreeOfParallelism, long ticks, ReadOnlyCollection distinctFilteredFaces) { List faces = new(); foreach (Face face in distinctFilteredFaces) { if (face.Mapping?.MappingFromFilter is null) throw new NotSupportedException(); if (face.Mapping.MappingFromFilter.IsUsed is not null && face.Mapping.MappingFromFilter.IsUsed.Value && face.Mapping.MappingFromFilter.IsFocusPerson is not null && !face.Mapping.MappingFromFilter.IsFocusPerson.Value) continue; if (face.FaceEncoding is not null && face.FaceDistance?.Encoding is not null && face.FaceDistance.Encoding is FaceRecognitionDotNet.FaceEncoding _) continue; faces.Add(face); } int totalSeconds = (int)Math.Floor(new TimeSpan(DateTime.Now.Ticks - ticks).TotalSeconds); ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = maxDegreeOfParallelism }; string message = $") {faces.Count:000} Load Face Encoding - {totalSeconds} total second(s)"; ProgressBarOptions options = new() { ProgressCharacter = '─', ProgressBarOnBottom = true, DisableBottomPercentage = true }; using ProgressBar progressBar = new(faces.Count, message, options); _ = Parallel.For(0, faces.Count, parallelOptions, (i, state) => { Face face = faces[i]; FaceRecognitionDotNet.FaceEncoding faceEncoding; if (face.FaceEncoding is null || face.Mapping?.MappingFromLocation is null) throw new NotSupportedException(); progressBar.Tick(); faceEncoding = FaceRecognition.LoadFaceEncoding(face.FaceEncoding.RawEncoding); FaceDistance faceDistance = new(face.Mapping.MappingFromLocation.ConfidencePercent, face.Mapping.MappingFromItem.GetDateTimeOriginalThenMinimumDateTime(), faceEncoding, face.Mapping.MappingFromFilterPost, face.Mapping.MappingFromItem.Id, face.Mapping.MappingFromItem.IsWrongYear, face.Mapping.MappingFromLocation.WholePercentages); lock (face) face.SetFaceDistance(faceDistance); }); } private static List GetSortingContainers(Map.Models.Configuration mapConfiguration, IDistanceLimits distanceLimits, Face face, FaceDistance faceDistanceEncoding, List sortingCollection) { List results = new(); SortingContainer sortingContainer; int days = 0, distance = 0; Sorting[] collection = Shared.Models.Stateless.Methods.ISorting.Sort(sortingCollection); foreach (Sorting sorting in collection) { if (face.Mapping?.MappingFromLocation is null || faceDistanceEncoding.WholePercentages is null) throw new NotSupportedException(); if (!mapConfiguration.SaveSortingWithoutPerson && face.Mapping.MappingFromPerson is null) continue; if (sorting.DaysDelta > distanceLimits.RangeDaysDeltaTolerance) { days += 1; continue; } if (sorting.DistancePermyriad > distanceLimits.FaceDistancePermyriad) { distance += 1; continue; } sortingContainer = new(face.Mapping, sorting); results.Add(sortingContainer); if (results.Count >= distanceLimits.SortingMaximumPerFaceShouldBeHigh) break; } distanceLimits.AddCounts(days, distance); return results; } private static List GetSortingCollection(Map.Models.MapLogic mapLogic, ReadOnlyCollection faceDistanceEncodings, int i, FaceDistance faceDistanceEncoding, Face face) { List results; List faceDistanceLengths = FaceRecognition.FaceDistances(faceDistanceEncodings, faceDistanceEncoding); results = mapLogic.GetSortingCollection(i, faceDistanceEncoding, face, faceDistanceLengths); return results; } public static ReadOnlyCollection GetFaceDistanceContainers(ReadOnlyCollection distinctFilteredFaces) { ReadOnlyCollection results; FaceDistance faceDistance; FaceDistanceContainer faceDistanceContainer; List collection = new(); foreach (Face face in distinctFilteredFaces) { if (face.Mapping?.MappingFromLocation is null) throw new NotSupportedException(); if (face.FaceDistance?.Encoding is not FaceRecognitionDotNet.FaceEncoding faceEncoding) continue; faceDistance = new(face.Mapping.MappingFromLocation.ConfidencePercent, face.Mapping.MappingFromItem.GetDateTimeOriginalThenMinimumDateTime(), faceEncoding, face.Mapping.MappingFromFilterPost, face.Mapping.MappingFromItem.Id, face.Mapping.MappingFromItem.IsWrongYear, face.Mapping.MappingFromLocation.WholePercentages); faceDistanceContainer = new(face, faceDistance); collection.Add(faceDistanceContainer); } results = new(collection.ToArray()); return results; } public static FaceDistanceContainer[] FilteredFaceDistanceContainers(Map.Models.MapLogic mapLogic, ReadOnlyCollection faceDistanceContainers, long? skipOlderThan, DistanceLimits distanceLimits) { List results = new(); foreach (FaceDistanceContainer faceDistanceContainer in faceDistanceContainers) { if (faceDistanceContainer.FaceDistance is null || faceDistanceContainer.Face.Mapping?.MappingFromLocation is null) throw new NotSupportedException(); if (skipOlderThan is not null && faceDistanceContainer.FaceDistance.DateTimeOriginalThenMinimumDateTime.Ticks < skipOlderThan.Value) continue; if (faceDistanceContainer.Face.Mapping.MappingFromLocation.ConfidencePercent < distanceLimits.FaceConfidencePercent) continue; if (faceDistanceContainer.Face.Mapping.MappingFromLocation.AreaPermyriad < distanceLimits.FaceAreaPermyriad) continue; if (faceDistanceContainer.Face.Mapping.MappingFromFilter.IsUsed is not null && faceDistanceContainer.Face.Mapping.MappingFromFilter.IsUsed.Value) continue; if (faceDistanceContainer.Face.Mapping.MappingFromFilter.IsFocusPerson is not null && !faceDistanceContainer.Face.Mapping.MappingFromFilter.IsFocusPerson.Value) continue; if (faceDistanceContainer.Face.Mapping.MappingFromFilter.IsFocusModel is not null && !faceDistanceContainer.Face.Mapping.MappingFromFilter.IsFocusModel.Value) continue; if (faceDistanceContainer.Face.Mapping.MappingFromFilter.IsFocusRelativePath is not null && !faceDistanceContainer.Face.Mapping.MappingFromFilter.IsFocusRelativePath.Value) continue; results.Add(faceDistanceContainer); } return results.ToArray(); } public static SortingContainer[] SetFaceMappingSortingCollectionThenGetSortingContainers(int maxDegreeOfParallelism, Map.Models.Configuration mapConfiguration, long ticks, Map.Models.MapLogic mapLogic, IDistanceLimits distanceLimits, ReadOnlyCollection faceDistanceEncodings, FaceDistanceContainer[] filteredFaceDistanceContainers) { SortingContainer[] results; List collection = new(); int totalSeconds = (int)Math.Floor(new TimeSpan(DateTime.Now.Ticks - ticks).TotalSeconds); ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = maxDegreeOfParallelism }; string message = $") {filteredFaceDistanceContainers.Length:000} Get Sorting Containers Then Set Face Mapping Sorting Collection - {totalSeconds} total second(s)"; ProgressBarOptions options = new() { ProgressCharacter = '─', ProgressBarOnBottom = true, DisableBottomPercentage = true }; using ProgressBar progressBar = new(filteredFaceDistanceContainers.Length, message, options); _ = Parallel.For(0, filteredFaceDistanceContainers.Length, parallelOptions, (i, state) => { progressBar.Tick(); FaceDistance faceDistanceEncoding = filteredFaceDistanceContainers[i].FaceDistance; Face face = filteredFaceDistanceContainers[i].Face; List sortingCollection = GetSortingCollection(mapLogic, faceDistanceEncodings, i, faceDistanceEncoding, face); if (sortingCollection.Count == 0) return; List sortingContainers = GetSortingContainers(mapConfiguration, distanceLimits, face, faceDistanceEncoding, sortingCollection); if (sortingContainers.Count > 0) { lock (collection) collection.AddRange(sortingContainers); } }); if (collection.Count == 0) results = Array.Empty(); else { if (distanceLimits.RangeDaysDeltaTargetLessThenUpper) results = Shared.Models.Stateless.Methods.ISortingContainer.Sort(collection); else results = Shared.Models.Stateless.Methods.ISortingContainer.SortUsingDaysDelta(collection); } return results; } private static void WriteVsCodeFiles(string eDistanceContentDirectory, string? displayDirectoryName, string directory) { string json; string vsCodeDirectory = Path.Combine(directory, ".vscode"); if (!Directory.Exists(vsCodeDirectory)) _ = Directory.CreateDirectory(vsCodeDirectory); if (displayDirectoryName is not null) File.WriteAllText(Path.Combine(directory, $"_ {displayDirectoryName}.txt"), string.Empty); json = "{ \"[markdown]\": { \"editor.wordWrap\": \"off\" }, \"foam.links.hover.enable\": false, \"foam.graph.style\": { \"background\": \"#202020\", \"node\": { \"note\": \"#f2cb1d\", \"distance\": \"green\", \"image\": \"orange\", \"placeholder\": \"white\", } } }"; _ = Shared.Models.Stateless.Methods.IPath.WriteAllText(Path.Combine(vsCodeDirectory, "settings.json"), json, updateDateWhenMatches: false, compareBeforeWrite: true, updateToWhenMatches: null); json = string.Concat("{ \"version\": \"2.0.0\", \"tasks\": [ { \"label\": \"MKLink\", \"type\": \"shell\", \"command\": \"New-Item\", \"args\": [ \"-ItemType\", \"Junction\", \"-Path\", \"'", directory.Replace('\\', '/'), "/()'\", \"-Target\", \"'", eDistanceContentDirectory.Replace('\\', '/'), "'\" ], \"problemMatcher\": [] } ] }"); _ = Shared.Models.Stateless.Methods.IPath.WriteAllText(Path.Combine(vsCodeDirectory, "tasks.json"), json, updateDateWhenMatches: false, compareBeforeWrite: true, updateToWhenMatches: null); } private static void MoveFiles(Dictionary keyValuePairs, List linkedOnce, List linkedTwice, List linkedThrice) { string checkFile; string checkDirectory; string? yearDirectory; string? personNameDirectory; string? personNameDirectoryName; string? personKeyFormattedDirectory; foreach (string file in linkedOnce) keyValuePairs[file] += 1; foreach (string file in linkedTwice) keyValuePairs[file] += 1; foreach (string file in linkedThrice) keyValuePairs[file] += 1; foreach (KeyValuePair keyValuePair in keyValuePairs) { personNameDirectory = Path.GetDirectoryName(keyValuePair.Key); yearDirectory = Path.GetDirectoryName(personNameDirectory); personNameDirectoryName = Path.GetFileName(personNameDirectory); personKeyFormattedDirectory = Path.GetDirectoryName(yearDirectory); if (string.IsNullOrEmpty(personNameDirectory) || string.IsNullOrEmpty(yearDirectory) || string.IsNullOrEmpty(personKeyFormattedDirectory) || string.IsNullOrEmpty(personNameDirectoryName)) continue; checkDirectory = Path.Combine(personKeyFormattedDirectory, $"{keyValuePair.Value}{new string(Convert.ToChar(65 + keyValuePair.Value), 7)}"); if (checkDirectory == yearDirectory) continue; checkDirectory = Path.Combine(checkDirectory, personNameDirectoryName); if (!Directory.Exists(checkDirectory)) _ = Directory.CreateDirectory(checkDirectory); checkFile = Path.Combine(checkDirectory, Path.GetFileName(keyValuePair.Key)); if (File.Exists(checkFile)) continue; File.Move(keyValuePair.Key, checkFile); } } private static void SaveMappedForOutputResolutions(string eDistanceContentDirectory, float distanceTolerance, long personKey, bool isCounterPersonYear, string personKeyFormatted, string? displayDirectoryName, List records, string directory) { int years; string text; string fileName; FileInfo fileInfo; List files = new(); List lines = new(); List results = new(); List linkedOnce = new(); long ticks = DateTime.Now.Ticks; string fileNameWithoutExtension; List linkedTwice = new(); List linkedThrice = new(); FaceDistance? faceDistanceEncoding; List relativePaths = new(); List faceDistanceLengths; Uri uri = new(eDistanceContentDirectory); Dictionary keyValuePairs = new(); List faceDistanceEncodings = new(); foreach (Record record in records) { files.Add(record.File); relativePaths.Add(uri.MakeRelativeUri(new(record.File)).OriginalString); faceDistanceEncodings.Add(new(record.FaceRecognitionDotNetFaceEncoding)); } foreach (Record record in records) { lines.Clear(); results.Clear(); fileInfo = new(record.File); if (files.Count > 1) { faceDistanceEncoding = new(record.FaceRecognitionDotNetFaceEncoding); if (faceDistanceEncoding is null) throw new NullReferenceException(nameof(faceDistanceEncoding)); faceDistanceLengths = FaceRecognition.FaceDistances(new(faceDistanceEncodings), faceDistanceEncoding); for (int i = 0; i < faceDistanceLengths.Count; i++) { fileName = Path.GetFileName(relativePaths[i]); if (fileName == fileInfo.Name) continue; FaceDistance faceDistance = faceDistanceLengths[i]; if (faceDistance.Length > distanceTolerance) continue; fileNameWithoutExtension = Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(fileName)); results.Add(new(files[i], faceDistance.Length, $"[[{fileNameWithoutExtension}]]", $"![{faceDistance.Length}]({relativePaths[i]})")); } } results = (from l in results orderby l.Length select l).Take(3).ToList(); keyValuePairs.Add(record.File, results.Count); foreach (Mapped mapped in results) { if (!linkedOnce.Contains(mapped.File)) { linkedOnce.Add(mapped.File); continue; } if (!linkedTwice.Contains(mapped.File)) { linkedTwice.Add(mapped.File); continue; } if (!linkedThrice.Contains(mapped.File)) { linkedThrice.Add(mapped.File); continue; } } if (isCounterPersonYear) (years, _) = Shared.Models.Stateless.Methods.IAge.GetAge(ticks, fileInfo.CreationTime.Ticks); else (years, _) = Shared.Models.Stateless.Methods.IAge.GetAge(fileInfo.CreationTime.Ticks, personKey); fileNameWithoutExtension = Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(fileInfo.Name)); lines.Add("---"); lines.Add("type: \"distance\""); lines.Add("---"); lines.Add(string.Empty); if (displayDirectoryName is null) lines.Add($"## {personKeyFormatted}"); else lines.Add($"## {displayDirectoryName}"); lines.Add(string.Empty); lines.Add($"![0]({uri.MakeRelativeUri(new(record.File)).OriginalString})"); lines.Add($"__{fileNameWithoutExtension}__"); if (isCounterPersonYear) lines.Add($"#{years}yrs-ago"); else lines.Add($"#{years}yrs-old"); lines.Add(string.Empty); lines.AddRange(results.Select(l => $"{l.Image}{Environment.NewLine}{l.Link}{Environment.NewLine}")); text = string.Join(Environment.NewLine, lines); if (!Shared.Models.Stateless.Methods.IPath.WriteAllText(Path.Combine(directory, $"{fileNameWithoutExtension}.md"), text, updateDateWhenMatches: false, compareBeforeWrite: true, updateToWhenMatches: null)) continue; } MoveFiles(keyValuePairs, linkedOnce, linkedTwice, linkedThrice); } void IDistance.SaveMappedForOutputResolutions(string a2PeopleContentDirectory, string eDistanceContentDirectory, float locationContainerDistanceTolerance, ReadOnlyCollection> locationContainers, long personKey, bool isCounterPersonYear, string personKeyFormatted, string? displayDirectoryName) { string? json; List records = new(); Shared.Models.FaceEncoding? modelsFaceEncoding; FaceRecognitionDotNet.FaceEncoding faceRecognitionDotNetFaceEncoding; foreach (LocationContainer locationContainer in locationContainers) { json = Metadata.Models.Stateless.Methods.IMetadata.GetFaceEncoding(locationContainer.Directories); if (json is null) continue; modelsFaceEncoding = JsonSerializer.Deserialize(json); if (modelsFaceEncoding is null) throw new NotSupportedException(); faceRecognitionDotNetFaceEncoding = FaceRecognition.LoadFaceEncoding(modelsFaceEncoding.RawEncoding); records.Add(new(locationContainer.File, faceRecognitionDotNetFaceEncoding)); } string directory = Path.Combine(a2PeopleContentDirectory, locationContainerDistanceTolerance.ToString(), personKeyFormatted); if (!Directory.Exists(directory)) _ = Directory.CreateDirectory(directory); if (records.Count > 0) { WriteVsCodeFiles(eDistanceContentDirectory, displayDirectoryName, directory); SaveMappedForOutputResolutions(eDistanceContentDirectory, locationContainerDistanceTolerance, personKey, isCounterPersonYear, personKeyFormatted, displayDirectoryName, records, directory); } _ = Shared.Models.Stateless.Methods.IPath.DeleteEmptyDirectories(directory); } }