diff --git a/Distance/Models/_E_Distance.cs b/Distance/Models/_E_Distance.cs index 20b0774..a13597b 100644 --- a/Distance/Models/_E_Distance.cs +++ b/Distance/Models/_E_Distance.cs @@ -1,10 +1,13 @@ using ShellProgressBar; using System.Collections.ObjectModel; +using System.Data; using System.Text.Json; using View_by_Distance.FaceRecognitionDotNet; +using View_by_Distance.Map.Models; using View_by_Distance.Property.Models.Stateless; using View_by_Distance.Shared.Models; using View_by_Distance.Shared.Models.Methods; +using View_by_Distance.Shared.Models.Stateless.Methods; namespace View_by_Distance.Distance.Models; @@ -136,11 +139,11 @@ public partial class E_Distance : IDistance return results; } - private static List<(Face, double?)> GetMatchingFacesByFaceEncoding(Face[] filteredFaces, string? json) + private static List<(Face, double?)> GetMatchingFacesByFaceEncoding(List faces, string? json) { List<(Face, double?)> results = []; string check; - foreach (Face face in filteredFaces) + foreach (Face face in faces) { if (json is null || face.FaceEncoding is null) continue; @@ -169,7 +172,7 @@ public partial class E_Distance : IDistance mappedFaceDirectory = Path.GetDirectoryName(file); if (mappedFaceDirectory is null) throw new NotSupportedException(); - deterministicHashCodeKey = Shared.Models.Stateless.Methods.IMapping.GetDeterministicHashCodeKey(filePath, face.Location, Shared.Models.Stateless.ILocation.Digits, face.OutputResolution); + deterministicHashCodeKey = IMapping.GetDeterministicHashCodeKey(filePath, face.Location, Shared.Models.Stateless.ILocation.Digits, face.OutputResolution); checkFile = Path.Combine(mappedFaceDirectory, $"{deterministicHashCodeKey}{mappingFromItem.FilePath.ExtensionLowered}{facesFileNameExtension}"); if (checkFile == file) continue; @@ -205,7 +208,7 @@ public partial class E_Distance : IDistance } } - public void LookForMatchFacesAndPossiblyRename(IDistanceLimits distanceLimits, Shared.Models.Stateless.Methods.IFaceD dFace, FilePath filePath, MappingFromItem mappingFromItem, ExifDirectory exifDirectory, List faces, ReadOnlyCollection locationContainers) + public void LookForMatchFacesAndPossiblyRename(bool overrideForFaceImages, IDistanceLimits distanceLimits, IFaceD dFace, FilePath filePath, MappingFromItem mappingFromItem, ExifDirectory exifDirectory, List faces, ReadOnlyCollection locationContainers) { string? json; string[] matches; @@ -213,9 +216,6 @@ public partial class E_Distance : IDistance List intersectFaces; Shared.Models.FaceEncoding? modelsFaceEncoding; List<(Face Face, double? Distance)> checkFaces = []; - 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.FilePath.FullName)) @@ -236,8 +236,8 @@ public partial class E_Distance : IDistance MoveUnableToMatch(locationContainer.FilePath); continue; } - if (filteredFaces.Length > 0) - checkFaces.AddRange(GetMatchingFacesByFaceEncoding(filteredFaces, json)); + if (faces.Count > 0) + checkFaces.AddRange(GetMatchingFacesByFaceEncoding(faces, json)); if (checkFaces.Count == 1) _Debug.Add(0); if (checkFaces.Count != 1 && !string.IsNullOrEmpty(json)) @@ -246,9 +246,9 @@ public partial class E_Distance : IDistance modelsFaceEncoding = JsonSerializer.Deserialize(json); if (modelsFaceEncoding is null) throw new NotSupportedException(); - if (filteredFaces.Length > 0) + if (faces.Count > 0) { - intersectFaces = Shared.Models.Stateless.Methods.ILocation.FilterByIntersect(filteredFaces, _RectangleIntersectMinimum, locationContainer.WholePercentages); + intersectFaces = Shared.Models.Stateless.Methods.ILocation.FilterByIntersect(faces, _RectangleIntersectMinimum, locationContainer.WholePercentages); if (intersectFaces.Count > 0) checkFaces.AddRange(GetClosestFaceByDistanceIgnoringTolerance(distanceLimits, mappingFromItem, intersectFaces, modelsFaceEncoding)); } @@ -279,11 +279,14 @@ public partial class E_Distance : IDistance } continue; } - if (checkFaces.Count == 1) + if (overrideForFaceImages) { json = Metadata.Models.Stateless.Methods.IMetadata.GetOutputResolution(locationContainer.ExifDirectory); - if (json is null || json.Contains(nameof(DateTime))) - dFace.ReSaveFace(exifDirectory, locationContainer, checkFaces[0].Face); + if (json is null || !json.Contains(nameof(DateTime))) + { + if (checkFaces.Count == 1) + dFace.ReSaveFace(exifDirectory, locationContainer.FilePath, checkFaces[0].Face, mappedFile: true); + } } if (_AllMappedFaceFileNames.Contains(locationContainer.FilePath.Name)) { @@ -332,7 +335,85 @@ public partial class E_Distance : IDistance File.WriteAllLines(eDistanceContentFileName, results); } - public static void PreFilterSetFaceDistances(int maxDegreeOfParallelism, Map.Models.Configuration configuration, long ticks, ReadOnlyCollection distinctValidImageFaces) + public static ReadOnlyDictionary> GetMappedWithEncoding(ReadOnlyDictionary> mapped) + { + Dictionary> results = []; + string? json; + LocationContainer? locationContainer; + Shared.Models.FaceEncoding? faceEncoding; + FaceRecognitionDotNet.FaceEncoding? encoding; + Dictionary keyValuePairs; + foreach (KeyValuePair> keyValuePair in mapped) + { + keyValuePairs = []; + foreach (KeyValuePair keyValue in keyValuePair.Value) + { + json = Metadata.Models.Stateless.Methods.IMetadata.GetFaceEncoding(keyValue.Value.ExifDirectory); + faceEncoding = json is null ? null : JsonSerializer.Deserialize(json); + if (faceEncoding is null) + continue; + encoding = FaceRecognition.LoadFaceEncoding(faceEncoding.RawEncoding); + locationContainer = LocationContainer.Get(keyValue.Value, encoding, keepExifDirectory: false); + keyValuePairs.Add(keyValue.Key, locationContainer); + } + results.Add(keyValuePair.Key, new(keyValuePairs)); + } + return new(results); + } + + public static List GetPreFilterLocationContainer(int maxDegreeOfParallelism, Configuration configuration, string focusDirectory, string focusModel, int? skipPersonWithMoreThen, long ticks, MapLogic mapLogic, long[] jLinkResolvedPersonKeys, ReadOnlyDictionary> mapped, List available) + { + List results = []; + string? json; + string? model; + bool? canReMap; + bool? isFocusPerson; + bool? inSkipCollection; + Shared.Models.FaceEncoding? faceEncoding; + FaceRecognitionDotNet.FaceEncoding? encoding; + ReadOnlyDictionary? keyValuePairs; + ReadOnlyDictionary>? wholePercentagesToPersonContainers; + foreach (LocationContainer locationContainer in available) + { + if (mapped.TryGetValue(locationContainer.Id, out keyValuePairs)) + { + if (keyValuePairs.ContainsKey(locationContainer.WholePercentages)) + continue; + } + if (locationContainer.ExifDirectory is null || locationContainer.FaceFile is null) + continue; + inSkipCollection = mapLogic.InSkipCollection(locationContainer.Id, locationContainer.WholePercentages); + if (inSkipCollection is not null && inSkipCollection.Value) + continue; + wholePercentagesToPersonContainers = mapLogic.GetWholePercentagesToPersonContainers(locationContainer.Id); + canReMap = Map.Models.Stateless.Methods.IMapLogic.CanReMap(jLinkResolvedPersonKeys, wholePercentagesToPersonContainers, locationContainer.WholePercentages); + if (canReMap is not null && !canReMap.Value) + continue; + isFocusPerson = mapLogic.IsFocusPerson(skipPersonWithMoreThen, jLinkResolvedPersonKeys, wholePercentagesToPersonContainers, locationContainer.WholePercentages); + if (isFocusPerson is not null && !isFocusPerson.Value) + continue; + if (!string.IsNullOrEmpty(focusModel)) + { + model = Metadata.Models.Stateless.Methods.IMetadata.GetModel(locationContainer.ExifDirectory); + if (string.IsNullOrEmpty(model) || !model.Contains(focusModel)) + continue; + } + if (!string.IsNullOrEmpty(focusDirectory)) + { + if (!locationContainer.FilePath.DirectoryName.Contains(focusDirectory)) + continue; + } + json = Metadata.Models.Stateless.Methods.IMetadata.GetFaceEncoding(locationContainer.ExifDirectory); + faceEncoding = json is null ? null : JsonSerializer.Deserialize(json); + if (faceEncoding is null) + continue; + encoding = FaceRecognition.LoadFaceEncoding(faceEncoding.RawEncoding); + results.Add(LocationContainer.Get(locationContainer, encoding, keepExifDirectory: false)); + } + return results; + } + + public static void PreFilterSetFaceDistances(int maxDegreeOfParallelism, Configuration configuration, long ticks, ReadOnlyCollection distinctValidImageFaces) { List faces = []; foreach (Face face in distinctValidImageFaces) @@ -372,12 +453,12 @@ public partial class E_Distance : IDistance }); } - private static List GetSortingContainers(Map.Models.Configuration mapConfiguration, IDistanceLimits distanceLimits, Face face, FaceDistance faceDistanceEncoding, List sortingCollection) + private static List GetSortingContainers(Configuration mapConfiguration, IDistanceLimits distanceLimits, Face face, FaceDistance faceDistanceEncoding, List sortingCollection) { List results = []; int days = 0, distance = 0; SortingContainer sortingContainer; - Sorting[] collection = Shared.Models.Stateless.Methods.ISorting.Sort(sortingCollection); + Sorting[] collection = ISorting.Sort(sortingCollection); foreach (Sorting sorting in collection) { if (face.Mapping?.MappingFromLocation is null || faceDistanceEncoding.WholePercentages is null) @@ -403,11 +484,11 @@ public partial class E_Distance : IDistance return results; } - private static List GetSortingCollection(Map.Models.MapLogic mapLogic, IDistanceLimits distanceLimits, ReadOnlyCollection faceDistanceEncodings, int i, Face face, FaceDistance faceDistanceEncoding) + private static List GetSortingCollection(MapLogic mapLogic, ReadOnlyCollection faceDistanceEncodings, int i, Face face, FaceDistance faceDistanceEncoding) { List results; List faceDistanceLengths = FaceRecognition.FaceDistances(faceDistanceEncodings, faceDistanceEncoding); - results = mapLogic.GetSortingCollection(distanceLimits, i, face, faceDistanceEncoding, faceDistanceLengths); + results = mapLogic.GetSortingCollection(i, face, faceDistanceEncoding, faceDistanceLengths); return results; } @@ -433,7 +514,23 @@ public partial class E_Distance : IDistance return results; } - public static FaceDistanceContainer[] FilteredPostLoadFaceDistanceContainers(Map.Models.MapLogic mapLogic, ReadOnlyCollection faceDistanceContainers, long? skipOlderThan, DistanceLimits distanceLimits) + public static List GetPostFilterLocationContainer(MapLogic mapLogic, List preFiltered, DistanceLimits distanceLimits) + { + List results = []; + foreach (LocationContainer locationContainer in preFiltered) + { + if (locationContainer.FaceFile is null) + continue; + if (locationContainer.FaceFile.AreaPermyriad < distanceLimits.FaceAreaPermyriad) + continue; + if (locationContainer.FaceFile.ConfidencePercent < distanceLimits.FaceConfidencePercent) + continue; + results.Add(locationContainer); + } + return results; + } + + public static FaceDistanceContainer[] FilteredPostLoadFaceDistanceContainers(MapLogic mapLogic, ReadOnlyCollection faceDistanceContainers, long? skipOlderThan, DistanceLimits distanceLimits) { List results = []; foreach (FaceDistanceContainer faceDistanceContainer in faceDistanceContainers) @@ -462,7 +559,46 @@ public partial class E_Distance : IDistance return results.ToArray(); } - public static ReadOnlyCollection SetFaceMappingSortingCollectionThenGetSortedSortingContainers(int maxDegreeOfParallelism, Map.Models.Configuration mapConfiguration, long ticks, Map.Models.MapLogic mapLogic, IDistanceLimits distanceLimits, ReadOnlyCollection faceDistanceEncodings, FaceDistanceContainer[] filteredFaceDistanceContainers) + private static ReadOnlyCollection GetReadOnlyLocationContainer(ReadOnlyDictionary> mappedWithEncoding, List postFiltered) + { + List results = []; + foreach (LocationContainer locationContainer in postFiltered) + results.Add(locationContainer); + foreach (KeyValuePair> keyValuePair in mappedWithEncoding) + { + foreach (KeyValuePair keyValue in keyValuePair.Value) + results.Add(keyValue.Value); + } + return new(results); + } + + public static ReadOnlyCollection GetMatrixLocationContainers(IDlibDotNet dlibDotNet, Configuration mapConfiguration, long ticks, MapLogic mapLogic, ReadOnlyDictionary> mappedWithEncoding, List preFiltered, DistanceLimits distanceLimits, List postFiltered) + { + List results = []; + ReadOnlyCollection locationContainers; + ReadOnlyCollection readOnlyLocationContainers = GetReadOnlyLocationContainer(mappedWithEncoding, postFiltered); + foreach (LocationContainer locationContainer in postFiltered) + { + dlibDotNet.Tick(); + locationContainers = FaceRecognition.GetLocationContainers(mapConfiguration.FaceDistancePermyriad, readOnlyLocationContainers, locationContainer); + foreach (LocationContainer item in locationContainers) + { + if (item.LengthPermyriad is null) + continue; + if (item.LengthPermyriad > distanceLimits.FaceDistancePermyriad) + break; + if (!mapConfiguration.SaveSortingWithoutPerson && item.PersonKey is null) + continue; + if (item.Id == locationContainer.Id && item.WholePercentages == locationContainer.WholePercentages) + continue; + results.Add(item); + } + } + LocationContainer[] array = results.OrderBy(l => l.LengthPermyriad).ToArray(); + return new(array); + } + + public static ReadOnlyCollection SetFaceMappingSortingCollectionThenGetSortedSortingContainers(int maxDegreeOfParallelism, Configuration mapConfiguration, long ticks, MapLogic mapLogic, IDistanceLimits distanceLimits, ReadOnlyCollection faceDistanceEncodings, FaceDistanceContainer[] filteredFaceDistanceContainers) { List results = []; int totalSeconds = (int)Math.Floor(new TimeSpan(DateTime.Now.Ticks - ticks).TotalSeconds); @@ -475,7 +611,7 @@ public partial class E_Distance : IDistance progressBar.Tick(); Face face = filteredFaceDistanceContainers[i].Face; FaceDistance faceDistanceEncoding = filteredFaceDistanceContainers[i].FaceDistance; - List sortingCollection = GetSortingCollection(mapLogic, distanceLimits, faceDistanceEncodings, i, face, faceDistanceEncoding); + List sortingCollection = GetSortingCollection(mapLogic, faceDistanceEncodings, i, face, faceDistanceEncoding); if (sortingCollection.Count == 0) return; List sortingContainers = GetSortingContainers(mapConfiguration, distanceLimits, face, faceDistanceEncoding, sortingCollection); @@ -486,9 +622,9 @@ public partial class E_Distance : IDistance } }); if (distanceLimits is not null && distanceLimits.RangeDaysDeltaTargetLessThenUpper) - results = Shared.Models.Stateless.Methods.ISortingContainer.Sort(results); + results = ISortingContainer.Sort(results); else - results = Shared.Models.Stateless.Methods.ISortingContainer.SortUsingDaysDelta(results); + results = ISortingContainer.SortUsingDaysDelta(results); return new(results); } diff --git a/Face/Models/_D_Face.cs b/Face/Models/_D_Face.cs index f5209d9..68189b8 100644 --- a/Face/Models/_D_Face.cs +++ b/Face/Models/_D_Face.cs @@ -6,6 +6,7 @@ using System.Text.Json; using System.Text.Json.Serialization; using View_by_Distance.FaceRecognitionDotNet; using View_by_Distance.Metadata.Models; +using View_by_Distance.Metadata.Models.Stateless.Methods; using View_by_Distance.Property.Models; using View_by_Distance.Resize.Models; using View_by_Distance.Shared.Models; @@ -149,7 +150,7 @@ public class D_Face : IFaceD #pragma warning disable CA1416 - private void SaveFaces(FileHolder resizedFileHolder, MetadataExtractor.GeoLocation? geoLocation, string? maker, string? model, List<(Shared.Models.Face, FileInfo?, string, bool)> collection) + private void SaveFaces(FileHolder resizedFileHolder, ExifDirectory exifDirectory, List<(Shared.Models.Face, FileHolder?, string, bool)> collection) { int width; int height; @@ -162,17 +163,27 @@ public class D_Face : IFaceD string faceFileJson; string faceEncodingJson; PropertyItem? propertyItem; + string? maker = IMetadata.GetMaker(exifDirectory); + string? model = IMetadata.GetModel(exifDirectory); using Bitmap source = new(resizedFileHolder.FullName); - int artist = MetadataExtractor.Formats.Exif.ExifDirectoryBase.TagArtist; - int userComment = MetadataExtractor.Formats.Exif.ExifDirectoryBase.TagUserComment; - foreach ((Shared.Models.Face face, FileInfo? fileInfo, string fileName, bool save) in collection) + MetadataExtractor.GeoLocation? geoLocation = IMetadata.GeoLocation(exifDirectory); + const int artist = MetadataExtractor.Formats.Exif.ExifDirectoryBase.TagArtist; // 315 + const int userComment = MetadataExtractor.Formats.Exif.ExifDirectoryBase.TagUserComment; + foreach ((Shared.Models.Face face, FileHolder? fileHolder, string fileName, bool save) in collection) { if (!save) continue; - if (fileInfo is null) + if (fileHolder is null) continue; if (face.FaceEncoding is null || face?.Location is null || face?.OutputResolution is null) continue; + if (_OverrideForFaceImages && fileHolder.Exists) + { + IFaceD dFace = this; + FilePath filePath = FilePath.Get(_PropertyConfiguration, fileHolder, index: null); + dFace.ReSaveFace(exifDirectory, filePath, face, mappedFile: false); + continue; + } location = Shared.Models.Stateless.Methods.ILocation.GetLocation(face.Location, Shared.Models.Stateless.ILocation.Digits, Shared.Models.Stateless.ILocation.Factor, source.Height, source.Width, collection.Count); if (location is null) continue; @@ -180,7 +191,15 @@ public class D_Face : IFaceD height = location.Bottom - location.Top; faceEncodingJson = JsonSerializer.Serialize(face.FaceEncoding); rectangle = new Rectangle(location.Left, location.Top, width, height); - faceFile = new(face.DateTime, geoLocation?.ToDmsString(), face.FaceParts, face.Location, maker, model, face.OutputResolution); + faceFile = new(face.Mapping?.MappingFromLocation?.AreaPermyriad, + face.Mapping?.MappingFromLocation?.ConfidencePercent, + face.DateTime, + geoLocation?.ToDmsString(), + face.FaceParts, + face.Location, + maker, + model, + face.OutputResolution); faceFileJson = JsonSerializer.Serialize(faceFile, FaceFileGenerationContext.Default.FaceFile); using (bitmap = new(width, height)) { @@ -190,7 +209,7 @@ public class D_Face : IFaceD bitmap.SetPropertyItem(propertyItem); propertyItem = Property.Models.Stateless.IProperty.GetPropertyItem(_ConstructorInfo, userComment, type, faceEncodingJson); bitmap.SetPropertyItem(propertyItem); - bitmap.Save(fileInfo.FullName, _ImageCodecInfo, _EncoderParameters); + bitmap.Save(fileHolder.FullName, _ImageCodecInfo, _EncoderParameters); } if (File.Exists(fileName)) File.Delete(fileName); @@ -330,11 +349,12 @@ public class D_Face : IFaceD return results; } - public List<(Shared.Models.Face, FileInfo?, string, bool)> SaveFaces(string f, string dResultsFullGroupDirectory, FilePath filePath, List> subFileTuples, List parseExceptions, MappingFromItem mappingFromItem, ExifDirectory exifDirectory, List faces) + public List<(Shared.Models.Face, FileHolder?, string, bool)> SaveFaces(string f, string dResultsFullGroupDirectory, FilePath filePath, List> subFileTuples, List parseExceptions, MappingFromItem mappingFromItem, ExifDirectory exifDirectory, List faces) { - List<(Shared.Models.Face, FileInfo?, string, bool Save)> results = []; + List<(Shared.Models.Face, FileHolder?, string, bool Save)> results = []; bool save; FileInfo fileInfo; + FileHolder fileHolder; string deterministicHashCodeKey; string[] changesFrom = [nameof(A_Property), nameof(B_Metadata), nameof(C_Resize)]; List dateTimes = (from l in subFileTuples where changesFrom.Contains(l.Item1) select l.Item2).ToList(); @@ -351,42 +371,78 @@ public class D_Face : IFaceD } deterministicHashCodeKey = Shared.Models.Stateless.Methods.IMapping.GetDeterministicHashCodeKey(filePath, face.Location, Shared.Models.Stateless.ILocation.Digits, face.OutputResolution); fileInfo = new FileInfo(Path.Combine(directory, $"{deterministicHashCodeKey}{mappingFromItem.FilePath.ExtensionLowered}{_FileNameExtension}")); + fileHolder = FileHolder.Get(fileInfo); if (!directoryExists) save = true; else if (_OverrideForFaceImages) save = true; - else if (!fileInfo.Exists) + else if (!fileHolder.Exists) save = true; else if (_CheckDFaceAndUpWriteDates && dateTimes.Count > 0 && dateTimes.Max() > fileInfo.LastWriteTime) save = true; - results.Add(new(face, fileInfo, Path.Combine(directory, $"{deterministicHashCodeKey}{mappingFromItem.FilePath.ExtensionLowered}{_HiddenFileNameExtension}"), save)); + results.Add(new(face, fileHolder, Path.Combine(directory, $"{deterministicHashCodeKey}{mappingFromItem.FilePath.ExtensionLowered}{_HiddenFileNameExtension}"), save)); } if (results.Any(l => l.Save)) { if (!directoryExists) _ = Directory.CreateDirectory(directory); - string? maker = Metadata.Models.Stateless.Methods.IMetadata.GetMaker(exifDirectory); - string? model = Metadata.Models.Stateless.Methods.IMetadata.GetModel(exifDirectory); - MetadataExtractor.GeoLocation? geoLocation = Metadata.Models.Stateless.Methods.IMetadata.GeoLocation(exifDirectory); - SaveFaces(mappingFromItem.ResizedFileHolder, geoLocation, maker, model, results); + SaveFaces(mappingFromItem.ResizedFileHolder, exifDirectory, results); } return results; } #pragma warning disable CA1416 - void IFaceD.ReSaveFace(ExifDirectory exifDirectory, LocationContainer locationContainer, Shared.Models.Face face) + private static (string?, string?) Get(string? json) { - FileInfo fileInfo = new(locationContainer.FilePath.FullName); + string? model; + string? maker; + FaceFile? faceFile = json is null ? null : JsonSerializer.Deserialize(json, FaceFileGenerationContext.Default.FaceFile); + if (faceFile is null || faceFile.Location is null) + (maker, model) = (null, null); + else + (maker, model) = (faceFile.Maker, faceFile.Model); + return (maker, model); + } + + void IFaceD.ReSaveFace(ExifDirectory exifDirectory, FilePath filePath, Shared.Models.Face face, bool mappedFile) + { + FileInfo fileInfo = new(filePath.FullName); if (fileInfo.Exists) { + string? json; short type = 2; - string checkFile = $"{locationContainer.FilePath.FullName}.exif"; - int artist = MetadataExtractor.Formats.Exif.ExifDirectoryBase.TagArtist; - string? maker = Metadata.Models.Stateless.Methods.IMetadata.GetMaker(exifDirectory); - string? model = Metadata.Models.Stateless.Methods.IMetadata.GetModel(exifDirectory); - MetadataExtractor.GeoLocation? geoLocation = Metadata.Models.Stateless.Methods.IMetadata.GeoLocation(exifDirectory); - FaceFile faceFile = new(face.DateTime, geoLocation?.ToDmsString(), face.FaceParts, face.Location, maker, model, face.OutputResolution); + string? model; + string? maker; + string checkFile = $"{filePath.FullName}.exif"; + const int artist = MetadataExtractor.Formats.Exif.ExifDirectoryBase.TagArtist; + // const int author = MetadataExtractor.Formats.Exif.ExifDirectoryBase.TagWinAuthor; // 40093 + if (mappedFile) + { + json = IMetadata.GetOutputResolution(exifDirectory); + if (json is not null && json.Contains(nameof(DateTime))) + return; + (maker, model) = Get(json); + } + else + { + maker = IMetadata.GetMaker(exifDirectory); + model = IMetadata.GetModel(exifDirectory); + ExifDirectory? faceExifDirectory = IMetadata.GetExifDirectory(filePath); + json = IMetadata.GetOutputResolution(faceExifDirectory); + if (json is not null && json.Contains(nameof(DateTime))) + return; + } + MetadataExtractor.GeoLocation? geoLocation = IMetadata.GeoLocation(exifDirectory); + FaceFile faceFile = new(face.Mapping?.MappingFromLocation?.AreaPermyriad, + face.Mapping?.MappingFromLocation?.ConfidencePercent, + face.DateTime, + geoLocation?.ToDmsString(), + face.FaceParts, + face.Location, + maker, + model, + face.OutputResolution); string faceFileJson = JsonSerializer.Serialize(faceFile, FaceFileGenerationContext.Default.FaceFile); ConstructorInfo? constructorInfo = typeof(PropertyItem).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public, null, [], null) ?? throw new Exception(); PropertyItem? propertyItem = Property.Models.Stateless.IProperty.GetPropertyItem(constructorInfo, artist, type, faceFileJson); diff --git a/FaceParts/Models/_D2_FaceParts.cs b/FaceParts/Models/_D2_FaceParts.cs index 99710f0..6b60154 100644 --- a/FaceParts/Models/_D2_FaceParts.cs +++ b/FaceParts/Models/_D2_FaceParts.cs @@ -302,12 +302,12 @@ public class D2_FaceParts #pragma warning disable CA1416 - private void SaveFaceLandmarkImage(MappingFromItem mappingFromItem, List<(Shared.Models.Face, FileInfo?, string, bool)> faceCollection, string fileName) + private void SaveFaceLandmarkImage(MappingFromItem mappingFromItem, List<(Shared.Models.Face, FileHolder?, string, bool)> faceCollection, string fileName) { Pen pen; using Image image = Image.FromFile(mappingFromItem.ResizedFileHolder.FullName); using Graphics graphic = Graphics.FromImage(image); - foreach ((Shared.Models.Face face, FileInfo? fileInfo, string _, bool _) in faceCollection) + foreach ((Shared.Models.Face face, FileHolder? _, string _, bool _) in faceCollection) { if (face.FaceEncoding is null || face.Location is null || face.OutputResolution is null || face.FaceParts is null || face.FaceParts.Count == 0) continue; @@ -327,17 +327,17 @@ public class D2_FaceParts #pragma warning restore CA1416 - private static bool GetNotMapped(string facePartsCollectionDirectory, List<(Shared.Models.Face Face, FileInfo?, string, bool)> faceCollection) + private static bool GetNotMapped(string facePartsCollectionDirectory, List<(Shared.Models.Face Face, FileHolder?, string, bool)> faceCollection) { bool results = false; string checkFile; - foreach ((Shared.Models.Face face, FileInfo? fileInfo, string _, bool _) in faceCollection) + foreach ((Shared.Models.Face face, FileHolder? fileHolder, string _, bool _) in faceCollection) { if (face.FaceEncoding is null || face.Location is null || face.OutputResolution is null) continue; - if (fileInfo is not null) + if (fileHolder is not null) { - checkFile = Path.Combine(facePartsCollectionDirectory, fileInfo.Name); + checkFile = Path.Combine(facePartsCollectionDirectory, fileHolder.Name); if (face.Mapping?.MappingFromPerson is not null) { if (File.Exists(checkFile)) @@ -353,14 +353,14 @@ public class D2_FaceParts if (!results) results = true; if (!File.Exists(checkFile)) - File.Copy(fileInfo.FullName, checkFile); + File.Copy(fileHolder.FullName, checkFile); } } } return results; } - public void CopyFacesAndSaveFaceLandmarkImage(string facePartsCollectionDirectory, MappingFromItem mappingFromItem, List<(Shared.Models.Face Face, FileInfo?, string, bool)> faceCollection) + public void CopyFacesAndSaveFaceLandmarkImage(string facePartsCollectionDirectory, MappingFromItem mappingFromItem, List<(Shared.Models.Face Face, FileHolder?, string, bool)> faceCollection) { bool hasNotMapped = GetNotMapped(facePartsCollectionDirectory, faceCollection); string fileName = Path.Combine(facePartsCollectionDirectory, $"{mappingFromItem.FilePath.Name}{_FileNameExtension}"); diff --git a/FaceRecognitionDotNet/FaceRecognition.cs b/FaceRecognitionDotNet/FaceRecognition.cs index b307aef..b340391 100644 --- a/FaceRecognitionDotNet/FaceRecognition.cs +++ b/FaceRecognitionDotNet/FaceRecognition.cs @@ -412,16 +412,42 @@ public class FaceRecognition : DisposableObject return null; } + public static ReadOnlyCollection GetLocationContainers(int permyriad, ReadOnlyCollection readOnlyLocationContainers, LocationContainer locationContainer) + { + List results = []; + int lengthPermyriad; + if (readOnlyLocationContainers.Count != 0) + { + double length; + LocationContainer result; + if (locationContainer.Encoding is not FaceEncoding faceEncodingToCompare) + throw new NullReferenceException(nameof(locationContainer)); + faceEncodingToCompare.ThrowIfDisposed(); + foreach (LocationContainer item in readOnlyLocationContainers) + { +#pragma warning disable CA1513 + if (item.Encoding is not FaceEncoding faceEncoding || faceEncoding.IsDisposed) + throw new ObjectDisposedException($"{nameof(item)} contains disposed object."); +#pragma warning restore CA1513 + using (Matrix diff = faceEncoding.Encoding - faceEncodingToCompare.Encoding) + length = DlibDotNet.Dlib.Length(diff); + lengthPermyriad = (int)(length * permyriad); + result = LocationContainer.Get(locationContainer, item, lengthPermyriad, keepExifDirectory: false, keepEncoding: false); + results.Add(result); + } + } + LocationContainer[] array = results.OrderBy(l => l.LengthPermyriad).ToArray(); + return new(array); + } + public static List FaceDistances(ReadOnlyCollection faceDistances, FaceDistance faceDistanceToCompare) { List results = []; - if (faceDistances is null) - throw new NullReferenceException(nameof(faceDistances)); if (faceDistances.Count != 0) { double length; FaceDistance result; - if (faceDistanceToCompare is null || faceDistanceToCompare.Encoding is not FaceEncoding faceEncodingToCompare) + if (faceDistanceToCompare.Encoding is not FaceEncoding faceEncodingToCompare) throw new NullReferenceException(nameof(faceDistanceToCompare)); faceEncodingToCompare.ThrowIfDisposed(); foreach (FaceDistance faceDistance in faceDistances) diff --git a/Instance/DlibDotNet.cs b/Instance/DlibDotNet.cs index 74d8ffe..6c4701d 100644 --- a/Instance/DlibDotNet.cs +++ b/Instance/DlibDotNet.cs @@ -286,6 +286,34 @@ public partial class DlibDotNet : IDlibDotNet, IDisposable } } + private void MapFaceFileLogic(long ticks, ReadOnlyCollection personContainers, MapLogic mapLogic, string? a2PeopleContentDirectory, string eDistanceContentDirectory, ProgressBarOptions options) + { + foreach (string outputResolution in _Configuration.OutputResolutions) + { + if (_Exceptions.Count != 0) + break; + if (!_Configuration.SaveResizedSubfiles) + continue; + if (!_ArgZeroIsConfigurationRootDirectory) + continue; + if (outputResolution != _Configuration.OutputResolutions[0]) + continue; + if (_PropertyRootExistedBefore || a2PeopleContentDirectory is null) + break; + if (!_Configuration.SaveFaceDistancesForOutputResolutions.Contains(outputResolution)) + continue; + if (!_Configuration.LoadOrCreateThenSaveDistanceResultsForOutputResolutions.Contains(outputResolution)) + continue; + List saveContainers = GetSaveContainers(ticks, personContainers, a2PeopleContentDirectory, eDistanceContentDirectory, options, mapLogic, outputResolution); + if (saveContainers.Count > 0) + { + int updated = 0; + mapLogic.SaveContainers(updated, saveContainers); + throw new NotSupportedException("Done Restart! :)"); + } + } + } + private void Search(long ticks, ReadOnlyCollection personContainers, string argZero, string propertyRoot) { int t; @@ -381,6 +409,8 @@ public partial class DlibDotNet : IDlibDotNet, IDisposable SaveDistinctIds(_Configuration.PropertyConfiguration, bResultsFullGroupDirectory, readOnlyContainers); mapLogic ??= new(_AppSettings.MaxDegreeOfParallelism, _Configuration.PropertyConfiguration, _MapConfiguration, _Distance, personContainers, ticks, a2PeopleContentDirectory, a2PeopleSingletonDirectory, eDistanceContentDirectory); DeleteContinueFiles(personContainers); + if (!runToDoCollectionFirst) + MapFaceFileLogic(ticks, personContainers, mapLogic, a2PeopleContentDirectory, eDistanceContentDirectory, options); FullDoWork(argZero, propertyRoot, ticks, aResultsFullGroupDirectory, bResultsFullGroupDirectory, fPhotoPrismSingletonDirectory, t, readOnlyContainers, propertyLogic, mapLogic); ReadOnlyCollection distinctValidImageItems = Shared.Models.Stateless.Methods.IContainer.GetValidImageItems(_Configuration.PropertyConfiguration, readOnlyContainers, distinctItems: true, filterItems: true); if (_Configuration.LookForAbandoned) @@ -389,7 +419,7 @@ public partial class DlibDotNet : IDlibDotNet, IDisposable string d2ResultsFullGroupDirectory; foreach (string outputResolution in _Configuration.OutputResolutions) { - _ProgressBar = new(5, nameof(mapLogic.LookForAbandoned), new ProgressBarOptions() { ProgressCharacter = '─', ProgressBarOnBottom = true, DisableBottomPercentage = true }); + _ProgressBar = new(5, nameof(mapLogic.LookForAbandoned), options); (cResultsFullGroupDirectory, _, dResultsFullGroupDirectory, d2ResultsFullGroupDirectory) = GetResultsFullGroupDirectories(outputResolution); mapLogic.LookForAbandoned(this, _Configuration.PropertyConfiguration, bResultsFullGroupDirectory, readOnlyContainers, cResultsFullGroupDirectory, dResultsFullGroupDirectory, d2ResultsFullGroupDirectory); _ProgressBar.Dispose(); @@ -805,6 +835,57 @@ public partial class DlibDotNet : IDlibDotNet, IDisposable } } + private ReadOnlyCollection GetFilePath(long ticks, string dFacesContentDirectory) + { + List results = []; + FilePath filePath; + FileHolder fileHolder; + string[] files = Directory.GetFiles(dFacesContentDirectory, $"*{_Faces.FileNameExtension}", SearchOption.AllDirectories); + long? skipOlderThan = _Configuration.SkipOlderThanDays is null ? null : new DateTime(ticks).AddDays(-_Configuration.SkipOlderThanDays.Value).Ticks; + foreach (string file in files) + { + fileHolder = Shared.Models.Stateless.Methods.IFileHolder.Get(file); + filePath = FilePath.Get(_Configuration.PropertyConfiguration, fileHolder, index: null); + if (filePath.Id is null) + continue; + if (skipOlderThan is not null && (fileHolder.LastWriteTime is null || fileHolder.LastWriteTime.Value.Ticks < skipOlderThan.Value)) + continue; + results.Add(filePath); + } + return new(results); + } + + private List GetSaveContainers(long ticks, ReadOnlyCollection personContainers, string a2PeopleSingletonDirectory, string eDistanceContentDirectory, ProgressBarOptions options, MapLogic mapLogic, string outputResolution) + { + List results; + long[] jLinkResolvedPersonKeys = _JLinkResolvedDirectories.Select(l => l.PersonKey).ToArray(); + (string cResultsFullGroupDirectory, string _, string dResultsFullGroupDirectory, string d2ResultsFullGroupDirectory) = GetResultsFullGroupDirectories(outputResolution); + string dFacesContentDirectory = Path.Combine(dResultsFullGroupDirectory, _Configuration.PropertyConfiguration.ResultContent); + ReadOnlyDictionary> mapped = Map.Models.Stateless.Methods.IMapLogic.GetMapped(_AppSettings.MaxDegreeOfParallelism, _Configuration.PropertyConfiguration, _MapConfiguration, ticks, personContainers, a2PeopleSingletonDirectory, eDistanceContentDirectory); + if (mapped.Count == 0 && !_Configuration.SaveSortingWithoutPerson) + throw new NotSupportedException($"Switch {nameof(_Configuration.SaveSortingWithoutPerson)}!"); + ReadOnlyCollection filePaths = GetFilePath(ticks, dFacesContentDirectory); + List available = Map.Models.Stateless.Methods.IMapLogic.GetAvailable(_AppSettings.MaxDegreeOfParallelism, _Configuration.PropertyConfiguration, _MapConfiguration, _Faces, ticks, dFacesContentDirectory, filePaths); + if (!string.IsNullOrEmpty(_Configuration.FocusDirectory) && _Configuration.FocusDirectory.Length != 2) + throw new NotSupportedException($"{nameof(_Configuration.FocusDirectory)} currently only works with output directory! Example 00."); + ReadOnlyDictionary> mappedWithEncoding = E_Distance.GetMappedWithEncoding(mapped); + if (mappedWithEncoding.Count == 0 && !_Configuration.SaveSortingWithoutPerson) + throw new NotSupportedException($"Switch {nameof(_Configuration.SaveSortingWithoutPerson)}!"); + List preFiltered = E_Distance.GetPreFilterLocationContainer(_AppSettings.MaxDegreeOfParallelism, _MapConfiguration, _Configuration.FocusDirectory, _Configuration.FocusModel, _Configuration.SkipPersonWithMoreThen, ticks, mapLogic, jLinkResolvedPersonKeys, mapped, available); + if (preFiltered.Count == 0) + throw new NotSupportedException("Done?"); + DistanceLimits distanceLimits = new(_Configuration.FaceAreaPermyriad, _Configuration.FaceConfidencePercent, _Configuration.FaceDistancePermyriad, _Configuration.RangeDaysDeltaTolerance, _Configuration.RangeDistanceTolerance, _Configuration.RangeFaceAreaPermyriadTolerance, _Configuration.RangeFaceConfidence, _Configuration.SortingMaximumPerFaceShouldBeHigh); + List postFiltered = E_Distance.GetPostFilterLocationContainer(mapLogic, preFiltered, distanceLimits); + if (postFiltered.Count == 0) + throw new NotSupportedException("Done?"); + string message = $") Building Matrix - {(int)Math.Floor(new TimeSpan(DateTime.Now.Ticks - ticks).TotalSeconds)} total second(s)"; + _ProgressBar = new(postFiltered.Count, message, options); + ReadOnlyCollection matrix = E_Distance.GetMatrixLocationContainers(this, _MapConfiguration, ticks, mapLogic, mappedWithEncoding, preFiltered, distanceLimits, postFiltered); + _ProgressBar.Dispose(); + results = mapLogic.GetSaveContainers(cResultsFullGroupDirectory, dResultsFullGroupDirectory, d2ResultsFullGroupDirectory, distanceLimits, matrix); + return results; + } + private void MapLogic(long ticks, ReadOnlyCollection containers, string fPhotoPrismContentDirectory, MapLogic mapLogic, string outputResolution, ReadOnlyDictionary> personKeyToIds, ReadOnlyCollection distinctValidImageFaces, ReadOnlyCollection distinctValidImageMappingCollection) { (_, _, string dResultsFullGroupDirectory, string d2ResultsFullGroupDirectory) = GetResultsFullGroupDirectories(outputResolution); @@ -1074,7 +1155,7 @@ public partial class DlibDotNet : IDlibDotNet, IDisposable bool move = _Configuration.DistanceMoveUnableToMatch || _Configuration.DistanceRenameToMatch && _Configuration.LoadOrCreateThenSaveDistanceResultsForOutputResolutions.Contains(outputResolution); faces = _Faces.GetFaces(outputResolution, dResultsFullGroupDirectory, item.FilePath, subFileTuples, parseExceptions, property, mappingFromItem, outputResolutionToResize, mappingFromPhotoPrismCollection); result = GetNotMappedCountAndUpdateMappingFromPersonThenSetMapping(mapLogic, item, isFocusRelativePath, mappingFromItem, mappingFromPhotoPrismCollection, faces); - List<(Shared.Models.Face, FileInfo?, string, bool Saved)> faceCollection = _Faces.SaveFaces(_FaceParts.FileNameExtension, dResultsFullGroupDirectory, item.FilePath, subFileTuples, parseExceptions, mappingFromItem, exifDirectory, faces); + List<(Shared.Models.Face, FileHolder?, string, bool Saved)> faceCollection = _Faces.SaveFaces(_FaceParts.FileNameExtension, dResultsFullGroupDirectory, item.FilePath, subFileTuples, parseExceptions, mappingFromItem, exifDirectory, faces); if (_Configuration.CopyFacesAndSaveFaceLandmarkForOutputResolutions.Contains(outputResolution)) _FaceParts.CopyFacesAndSaveFaceLandmarkImage(facePartsCollectionDirectory, mappingFromItem, faceCollection); if (move && faceCollection.All(l => !l.Saved)) @@ -1085,7 +1166,7 @@ public partial class DlibDotNet : IDlibDotNet, IDisposable Map.Models.Stateless.Methods.IMapLogic.SetCreationTime(mappingFromItem, locationContainers); if (_Configuration.LocationContainerDistanceTolerance is null && _Configuration.MoveToDecade) Map.Models.Stateless.Methods.IMapLogic.MoveToDecade(_Configuration.PropertyConfiguration, mappingFromItem, locationContainers); - _Distance.LookForMatchFacesAndPossiblyRename(_DistanceLimits, _Faces, item.FilePath, mappingFromItem, exifDirectory, faces, locationContainers); + _Distance.LookForMatchFacesAndPossiblyRename(_Configuration.OverrideForFaceImages, _DistanceLimits, _Faces, item.FilePath, mappingFromItem, exifDirectory, faces, locationContainers); } } (bool review, int[] eyesCollection) = Shared.Models.Stateless.Methods.IFace.GetEyeCollection(_Configuration.EyeThreshold, faces); @@ -1250,8 +1331,7 @@ public partial class DlibDotNet : IDlibDotNet, IDisposable if (filteredFaceDistanceContainers.Length > 0) { int updated = sortingContainers.Count == 0 ? 0 : mapLogic.UpdateFromSortingContainers(dFacesContentDirectory, d2FacePartsContentDirectory, d2FacePartsContentCollectionDirectory, idToWholePercentagesToMapping, sortingContainers); - List saveContainers; - saveContainers = mapLogic.GetSaveContainers(dFacesContentDirectory, d2FacePartsContentDirectory, d2FacePartsContentCollectionDirectory, idToWholePercentagesToMapping, distanceLimits, useFiltersCounter, sortingContainers); + List saveContainers = mapLogic.GetSaveContainers(dFacesContentDirectory, d2FacePartsContentDirectory, d2FacePartsContentCollectionDirectory, idToWholePercentagesToMapping, distanceLimits, useFiltersCounter, sortingContainers); if (saveContainers.Count > 0) mapLogic.SaveContainers(updated, saveContainers); } diff --git a/Map/.vscode/format-report.json b/Map/.vscode/format-report.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/Map/.vscode/format-report.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/Map/.vscode/tasks.json b/Map/.vscode/tasks.json new file mode 100644 index 0000000..a35e3d5 --- /dev/null +++ b/Map/.vscode/tasks.json @@ -0,0 +1,42 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Format", + "command": "dotnet", + "type": "process", + "args": [ + "format", + "--report", + ".vscode", + "--verbosity", + "detailed", + "--severity", + "warn" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "Format-Whitespaces", + "command": "dotnet", + "type": "process", + "args": [ + "format", + "whitespace" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/Map.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/Map/Models/MapLogic.cs b/Map/Models/MapLogic.cs index bc3688b..277521e 100644 --- a/Map/Models/MapLogic.cs +++ b/Map/Models/MapLogic.cs @@ -460,6 +460,31 @@ public partial class MapLogic : Shared.Models.Methods.IMapLogic return result; } + private (long?, string?) GetDirectory(Configuration configuration, string by, string segmentB) + { + long? ticks = null; + const int zero = 0; + string? directory = null; + string personKeyFormatted; + PersonBirthday personBirthday; + PersonContainer personContainer; + for (int i = _NotMappedPersonContainers.Count - 1; i > 0; i--) + { + if (configuration.SaveIndividually) + break; + personContainer = _NotMappedPersonContainers[i]; + if (personContainer.Key is null || personContainer.Birthdays is null || personContainer.Birthdays.Length == 0) + continue; + personBirthday = personContainer.Birthdays[zero]; + ticks = personBirthday.Value.Ticks; + personKeyFormatted = IPersonBirthday.GetFormatted(configuration.PersonBirthdayFormat, personBirthday); + directory = Path.Combine(_EDistanceContentTicksDirectory, by, personKeyFormatted, segmentB); + _NotMappedPersonContainers.RemoveAt(i); + break; + } + return (ticks, directory); + } + private (long?, string?) GetDirectory(Configuration configuration, bool saveIndividually, int padLeft, string? segmentC, string by, MappingFromItem mappingFromItem) { long? ticks = null; @@ -512,7 +537,30 @@ public partial class MapLogic : Shared.Models.Methods.IMapLogic return result; } - private Record Get(Configuration configuration, bool saveIndividually, string by, Mapping question, int padLeft) + private Record Get(Configuration configuration, string by, long? personKey, string? displayDirectoryName, string segmentB) + { + long? ticks; + string? directory; + string? debugDirectory; + string? personDirectory; + if (personKey is null || string.IsNullOrEmpty(displayDirectoryName)) + { + debugDirectory = null; + (ticks, directory) = GetDirectory(configuration, by, segmentB); + personDirectory = directory is null ? null : Path.Combine(directory, $"X+{ticks}"); + } + else + { + ticks = null; + string personKeyFormatted = IPersonBirthday.GetFormatted(configuration.PersonBirthdayFormat, personKey.Value); + debugDirectory = Path.Combine(_EDistanceContentTicksDirectory, by, personKeyFormatted, displayDirectoryName); + directory = Path.Combine(_EDistanceContentTicksDirectory, by, personKeyFormatted, segmentB); + personDirectory = Path.Combine(directory, displayDirectoryName, "lnk"); + } + return new(debugDirectory, directory, ticks, personDirectory); + } + + private Record Get(Configuration configuration, bool saveIndividually, int padLeft, string by, Mapping question) { long? ticks; string? directory; @@ -594,7 +642,7 @@ public partial class MapLogic : Shared.Models.Methods.IMapLogic if (!PreAndPostContinue(_Configuration, mapping, question)) continue; } - record = Get(_Configuration, saveIndividually, by, mapping, padLeft); + record = Get(_Configuration, saveIndividually, padLeft, by, mapping); if (string.IsNullOrEmpty(record.Directory) || string.IsNullOrEmpty(record.PersonDirectory)) continue; directory = record.Directory; @@ -673,7 +721,7 @@ public partial class MapLogic : Shared.Models.Methods.IMapLogic Stateless.MapLogic.SaveMappingShortcuts(mappingDirectory); } - public List GetSortingCollection(Shared.Models.Methods.IDistanceLimits distanceLimits, int i, Face face, FaceDistance faceDistanceEncoding, List faceDistanceLengths) + public List GetSortingCollection(int i, Face face, FaceDistance faceDistanceEncoding, List faceDistanceLengths) { if (_Configuration is null) throw new NullReferenceException(nameof(_Configuration)); @@ -772,6 +820,94 @@ public partial class MapLogic : Shared.Models.Methods.IMapLogic return result; } + private string? GetDisplayDirectoryName(string? displayDirectoryName, LocationContainer locationContainer) + { + string? result = displayDirectoryName; + ReadOnlyDictionary>? wholePercentagesToPersonContainers = GetWholePercentagesToPersonContainers(locationContainer.Id); + if (wholePercentagesToPersonContainers is not null) + { + foreach (KeyValuePair> keyValuePair in wholePercentagesToPersonContainers) + { + if (keyValuePair.Key != locationContainer.WholePercentages) + continue; + if (keyValuePair.Value.Count != 1) + continue; + result = keyValuePair.Value[0].DisplayDirectoryName; + } + } + return result; + } + + public List GetSaveContainers(string cResultsFullGroupDirectory, string dResultsFullGroupDirectory, string d2ResultsFullGroupDirectory, Shared.Models.Methods.IDistanceLimits distanceLimits, ReadOnlyCollection matrix) + { + if (_Configuration is null) + throw new NullReferenceException(nameof(_Configuration)); + List results = []; + string by; + Record record; + string segmentB; + bool isBySorting; + string checkFile; + string? directory; + string shortcutFile; + string facesDirectory; + List added = []; + bool isCounterPersonYear; + string facePartsDirectory; + FileHolder? faceFileHolder; + SaveContainer? saveContainer; + string? displayDirectoryName; + FileHolder? resizedFileHolder; + int? useFiltersCounter = null; + string resizeContentDirectory; + FileHolder? facePartsFileHolder; + FileHolder? hiddenFaceFileHolder; + bool sortingContainersAny = matrix.Count > 0; + string cContentDirectory = Path.Combine(cResultsFullGroupDirectory, _Configuration.PropertyConfiguration.ResultContent); + string forceSingleImageHumanized = nameof(Shared.Models.Stateless.IMapLogic.ForceSingleImage).Humanize(LetterCasing.Title); + string d2FacePartsContentDirectory = Path.Combine(d2ResultsFullGroupDirectory, _Configuration.PropertyConfiguration.ResultContent); + foreach (LocationContainer locationContainer in matrix) + { + if (_Configuration.SaveIndividually) + break; + if (locationContainer.LengthPermyriad is null || locationContainer.LengthSource is null) + continue; + if (added.Contains(locationContainer.LengthSource.Name)) + continue; + segmentB = locationContainer.LengthPermyriad.Value.ToString().PadLeft(2, '0')[..2]; + isCounterPersonYear = locationContainer.PersonKey is not null && IPersonBirthday.IsCounterPersonYear(locationContainer.PersonKey.Value); + displayDirectoryName = isCounterPersonYear ? locationContainer.DisplayDirectoryName : GetDisplayDirectoryName(locationContainer.DisplayDirectoryName, locationContainer); + (by, _, isBySorting) = Stateless.MapLogic.Get(useFiltersCounter, _Configuration.SaveIndividually, sortingContainersAny, forceSingleImageHumanized, locationContainer.LengthPermyriad, locationContainer.PersonKey, displayDirectoryName); + record = Get(_Configuration, by, locationContainer.PersonKey, displayDirectoryName, segmentB); + if (string.IsNullOrEmpty(record.Directory) || string.IsNullOrEmpty(record.PersonDirectory)) + continue; + directory = record.Directory; + if (!string.IsNullOrEmpty(record.DebugDirectory)) + results.Add(new(record.DebugDirectory)); + if (locationContainer.PersonKey is null) + { + if (!_Configuration.SaveSortingWithoutPerson) + throw new NotSupportedException(); + if (record.Ticks is null) + continue; + } + results.Add(new(record.PersonDirectory)); + facesDirectory = locationContainer.LengthSource.DirectoryName; + faceFileHolder = IFileHolder.Get(locationContainer.LengthSource.FullName); + checkFile = Path.Combine(directory, $"{locationContainer.LengthSource.Name}"); + shortcutFile = Path.Combine(record.PersonDirectory, $"{locationContainer.LengthSource.Name}.lnk"); + resizeContentDirectory = Stateless.MapLogic.GetResizeContentDirectory(_PropertyConfiguration, cContentDirectory, locationContainer.LengthSource); + facePartsDirectory = Stateless.MapLogic.GetFacePartsDirectoryX(_PropertyConfiguration, d2FacePartsContentDirectory, locationContainer.LengthSource); + hiddenFaceFileHolder = IFileHolder.Get(Path.Combine(facesDirectory, $"{locationContainer.LengthSource.NameWithoutExtension}{_Configuration.FacesHiddenFileNameExtension}")); + facePartsFileHolder = IFileHolder.Get(Path.Combine(facePartsDirectory, $"{locationContainer.LengthSource.NameWithoutExtension}{_Configuration.FacePartsFileNameExtension}")); + resizedFileHolder = IFileHolder.Get(Path.Combine(resizeContentDirectory, $"{locationContainer.LengthSource.FileNameFirstSegment}{Path.GetExtension(locationContainer.LengthSource.NameWithoutExtension)}")); + saveContainer = new(checkFile, directory, faceFileHolder, hiddenFaceFileHolder, facePartsFileHolder, resizedFileHolder, shortcutFile); + results.Add(saveContainer); + added.Add(locationContainer.LengthSource.Name); + } + return results; + } + public List GetSaveContainers(string dFacesContentDirectory, string d2FacePartsContentDirectory, string d2FacePartsContentCollectionDirectory, ReadOnlyDictionary> idToWholePercentagesToMapping, Shared.Models.Methods.IDistanceLimits distanceLimits, int? useFiltersCounter, ReadOnlyCollection sortingContainers) { if (_Configuration is null) @@ -810,7 +946,7 @@ public partial class MapLogic : Shared.Models.Methods.IMapLogic throw new NotSupportedException(); if (question.MappingFromLocation is null) continue; - record = Get(_Configuration, _Configuration.SaveIndividually, by, question, padLeft); + record = Get(_Configuration, _Configuration.SaveIndividually, padLeft, by, question); if (string.IsNullOrEmpty(record.Directory) || string.IsNullOrEmpty(record.PersonDirectory)) continue; directory = record.Directory; @@ -1320,10 +1456,13 @@ public partial class MapLogic : Shared.Models.Methods.IMapLogic return result; } - public bool InSkipCollection(int id, MappingFromLocation mappingFromLocation) => - _SkipCollection.TryGetValue(id, out List? wholePercentagesCollection) && wholePercentagesCollection.Contains(mappingFromLocation.WholePercentages); + public bool InSkipCollection(int id, int wholePercentages) => + _SkipCollection.TryGetValue(id, out List? wholePercentagesCollection) && wholePercentagesCollection.Contains(wholePercentages); - public bool? IsFocusPerson(int? skipPersonWithMoreThen, long[] jLinkResolvedPersonKeys, ReadOnlyDictionary>? wholePercentagesToPersonContainers, MappingFromLocation mappingFromLocation) + public bool InSkipCollection(int id, MappingFromLocation mappingFromLocation) => + InSkipCollection(id, mappingFromLocation.WholePercentages); + + public bool? IsFocusPerson(int? skipPersonWithMoreThen, long[] jLinkResolvedPersonKeys, ReadOnlyDictionary>? wholePercentagesToPersonContainers, int wholePercentages) { bool? result; ReadOnlyCollection? personContainers; @@ -1331,7 +1470,7 @@ public partial class MapLogic : Shared.Models.Methods.IMapLogic result = null; else if (wholePercentagesToPersonContainers is null) result = null; - else if (!wholePercentagesToPersonContainers.TryGetValue(mappingFromLocation.WholePercentages, out personContainers)) + else if (!wholePercentagesToPersonContainers.TryGetValue(wholePercentages, out personContainers)) result = null; else { @@ -1355,6 +1494,9 @@ public partial class MapLogic : Shared.Models.Methods.IMapLogic return result; } + public bool? IsFocusPerson(int? skipPersonWithMoreThen, long[] jLinkResolvedPersonKeys, ReadOnlyDictionary>? wholePercentagesToPersonContainers, MappingFromLocation mappingFromLocation) => + IsFocusPerson(skipPersonWithMoreThen, jLinkResolvedPersonKeys, wholePercentagesToPersonContainers, mappingFromLocation.WholePercentages); + public void LookForAbandoned(IDlibDotNet dlibDotNet, Property.Models.Configuration propertyConfiguration, string bResultsFullGroupDirectory, ReadOnlyCollection readOnlyContainers, string cResultsFullGroupDirectory, string dResultsFullGroupDirectory, string d2ResultsFullGroupDirectory) { string[] directories; diff --git a/Map/Models/Stateless/DistanceLogic.cs b/Map/Models/Stateless/DistanceLogic.cs index 351c628..5b1e341 100644 --- a/Map/Models/Stateless/DistanceLogic.cs +++ b/Map/Models/Stateless/DistanceLogic.cs @@ -387,7 +387,7 @@ internal abstract class DistanceLogic } personNameDirectories = Directory.GetDirectories(yearDirectory, "*", SearchOption.TopDirectoryOnly); if (personNameDirectories.Length > 1) - throw new NotSupportedException(); + throw new NotSupportedException("Try deleting *.lnk files!"); foreach (string personNameDirectory in personNameDirectories) { directoryNumber++; diff --git a/Map/Models/Stateless/FaceFileLogic.cs b/Map/Models/Stateless/FaceFileLogic.cs new file mode 100644 index 0000000..db06a67 --- /dev/null +++ b/Map/Models/Stateless/FaceFileLogic.cs @@ -0,0 +1,212 @@ +using ShellProgressBar; +using System.Collections.ObjectModel; +using System.Drawing; +using System.Text.Json; +using View_by_Distance.Shared.Models; +using View_by_Distance.Shared.Models.Stateless.Methods; +using static View_by_Distance.Map.Models.Stateless.MapLogic; + +namespace View_by_Distance.Map.Models.Stateless; + +internal abstract class FaceFileLogic +{ + + private static void MappedParallelFor(Shared.Models.Properties.IPropertyConfiguration propertyConfiguration, Configuration configuration, ReadOnlyDictionary> skipCollection, List locationContainers, MappedFile mappedFile) + { + int? id; + string checkFile; + DateOnly dateOnly; + FilePath filePath; + string[] fileMatches; + FileHolder fileHolder; + int? wholePercentages; + const string lnk = ".lnk"; + ExifDirectory? exifDirectory; + string personDisplayDirectoryName; + const bool fromDistanceContent = true; + List<(string File, int WholePercentages)>? wholePercentagesCollection; + if (!mappedFile.FilePath.Name.EndsWith(lnk)) + { + if (mappedFile.FilePath.Id is null) + return; + id = mappedFile.FilePath.Id; + wholePercentages = IMapping.GetWholePercentages(configuration.FacesFileNameExtension, mappedFile.FilePath); + } + else + { + fileHolder = IFileHolder.Get(mappedFile.FilePath.FullName[..^4]); + filePath = FilePath.Get(propertyConfiguration, fileHolder, index: null); + if (filePath.Id is null) + return; + id = filePath.Id; + wholePercentages = IMapping.GetWholePercentages(configuration.FacesFileNameExtension, filePath); + } + if (wholePercentages is null) + return; + if (configuration.LinkedAlpha is null && string.IsNullOrEmpty(configuration.LocationContainerDebugDirectory) && skipCollection.TryGetValue(id.Value, out wholePercentagesCollection)) + { + fileMatches = (from l in wholePercentagesCollection where l.WholePercentages == wholePercentages select l.File).ToArray(); + foreach (string fileMatch in fileMatches) + { + if (string.IsNullOrEmpty(fileMatch) || !File.Exists(fileMatch)) + continue; + checkFile = $"{fileMatch}.dup"; + if (File.Exists(checkFile)) + continue; + File.Move(fileMatch, checkFile); + continue; + } + } + dateOnly = DateOnly.FromDateTime(new DateTime(mappedFile.FilePath.CreationTicks)); + if (mappedFile.FilePath.Name.EndsWith(lnk) || !File.Exists(mappedFile.FilePath.FullName)) + exifDirectory = null; + else + exifDirectory = Metadata.Models.Stateless.Methods.IMetadata.GetExifDirectory(mappedFile.FilePath); + RectangleF? rectangle = ILocation.GetPercentagesRectangle(configuration.LocationDigits, wholePercentages.Value); + personDisplayDirectoryName = mappedFile.PersonDisplayDirectoryName is null ? configuration.MappingDefaultName : mappedFile.PersonDisplayDirectoryName; + lock (locationContainers) + locationContainers.Add(new(dateOnly, + exifDirectory, + mappedFile.DirectoryNumber, + personDisplayDirectoryName, + null, + null, + mappedFile.FilePath, + fromDistanceContent, + id.Value, + null, + null, + mappedFile.PersonKey, + rectangle, + wholePercentages.Value)); + } + + private static ReadOnlyDictionary> GetReadOnly(Dictionary> keyValuePairs) + { + Dictionary> results = []; + foreach (KeyValuePair> keyValuePair in keyValuePairs) + results.Add(keyValuePair.Key, new(keyValuePair.Value)); + return new(results); + } + + internal static ReadOnlyDictionary> GetMapped(int maxDegreeOfParallelism, Property.Models.Configuration propertyConfiguration, Configuration configuration, long ticks, ReadOnlyCollection personContainers, string a2PeopleSingletonDirectory, string eDistanceContentDirectory) + { + Dictionary> results = []; + List locationContainers = []; + Dictionary? keyValuePairs; + Dictionary> skipCollection = []; + Dictionary> skipNotSkipCollection = []; + ReadOnlyCollection readOnlyPersonKeyFormattedCollection; + ReadOnlyDictionary readOnlyPersonKeyFormattedToNewestPersonKeyFormatted; + SetSkipCollections(configuration, personContainers, a2PeopleSingletonDirectory, skipCollection, skipNotSkipCollection); + { + List personKeyFormattedCollection = []; + Dictionary personKeyFormattedToNewestPersonKeyFormatted = []; + SetPersonCollections(configuration, personContainers, personKeyFormattedToNewestPersonKeyFormatted, personKeyFormattedCollection); + readOnlyPersonKeyFormattedCollection = new(personKeyFormattedCollection); + readOnlyPersonKeyFormattedToNewestPersonKeyFormatted = new(personKeyFormattedToNewestPersonKeyFormatted); + } + List records = DistanceLogic.DeleteEmptyDirectoriesAndGetCollection(propertyConfiguration, configuration, ticks, eDistanceContentDirectory, readOnlyPersonKeyFormattedToNewestPersonKeyFormatted, readOnlyPersonKeyFormattedCollection); + List mappedFiles = GetMappedFiles(propertyConfiguration, configuration, personContainers, records); + if (mappedFiles.Count > 0) + { + int totalSeconds = (int)Math.Floor(new TimeSpan(DateTime.Now.Ticks - ticks).TotalSeconds); + string message = $") Building Mapped Face Files Collection - {totalSeconds} total second(s)"; + ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = maxDegreeOfParallelism }; + ReadOnlyDictionary> readOnlySkipNotSkipCollection = new(skipCollection); + ProgressBarOptions options = new() { ProgressCharacter = '─', ProgressBarOnBottom = true, DisableBottomPercentage = true }; + using ProgressBar progressBar = new(mappedFiles.Count, message, options); + _ = Parallel.For(0, mappedFiles.Count, parallelOptions, (i, state) => + { + progressBar.Tick(); + MappedParallelFor(propertyConfiguration, configuration, readOnlySkipNotSkipCollection, locationContainers, mappedFiles[i]); + }); + } + foreach (LocationContainer locationContainer in locationContainers) + { + if (!results.TryGetValue(locationContainer.Id, out keyValuePairs)) + { + results.Add(locationContainer.Id, []); + if (!results.TryGetValue(locationContainer.Id, out keyValuePairs)) + throw new Exception(); + } + if (keyValuePairs.ContainsKey(locationContainer.WholePercentages)) + continue; + keyValuePairs.Add(locationContainer.WholePercentages, locationContainer); + } + return GetReadOnly(results); + } + + private static void MoveUnableToMatch(FilePath filePath) + { + string checkFile = $"{filePath.FullName}.unk"; + if (File.Exists(filePath.FullName) && !File.Exists(checkFile)) + File.Move(filePath.FullName, checkFile); + } + + private static void AvailableParallelFor(Shared.Models.Properties.IPropertyConfiguration propertyConfiguration, Configuration configuration, IFaceD dFace, List locationContainers, FilePath filePath) + { + string? json; + const bool fromDistanceContent = false; + if (filePath.Id is null) + return; + DateOnly dateOnly = DateOnly.FromDateTime(new DateTime(filePath.CreationTicks)); + int? wholePercentages = IMapping.GetWholePercentages(dFace.FileNameExtension, filePath); + if (wholePercentages is null) + { + if (configuration.DistanceMoveUnableToMatch) + MoveUnableToMatch(filePath); + return; + } + ExifDirectory exifDirectory = Metadata.Models.Stateless.Methods.IMetadata.GetExifDirectory(filePath); + json = Metadata.Models.Stateless.Methods.IMetadata.GetOutputResolution(exifDirectory); + if (json is null || !json.Contains(nameof(DateTime))) + { + if (configuration.DistanceMoveUnableToMatch) + MoveUnableToMatch(filePath); + return; + } + FaceFile? faceFile = JsonSerializer.Deserialize(json, FaceFileGenerationContext.Default.FaceFile); + if (faceFile is null || faceFile.Location is null) + { + if (configuration.DistanceMoveUnableToMatch) + MoveUnableToMatch(filePath); + return; + } + RectangleF? rectangle = ILocation.GetPercentagesRectangle(configuration.LocationDigits, wholePercentages.Value); + if (rectangle is null) + return; + lock (locationContainers) + locationContainers.Add(new(dateOnly, + exifDirectory, + null, + null, + null, + faceFile, + filePath, + fromDistanceContent, + filePath.Id.Value, + null, + null, + null, + rectangle, + wholePercentages.Value)); + } + + internal static List GetAvailable(int maxDegreeOfParallelism, Property.Models.Configuration propertyConfiguration, Configuration configuration, IFaceD dFace, long ticks, string dFacesContentDirectory, ReadOnlyCollection filePaths) + { + List results = []; + int totalSeconds = (int)Math.Floor(new TimeSpan(DateTime.Now.Ticks - ticks).TotalSeconds); + ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = maxDegreeOfParallelism }; + string message = $") Building Available Face Files Collection - {totalSeconds} total second(s)"; + ProgressBarOptions options = new() { ProgressCharacter = '─', ProgressBarOnBottom = true, DisableBottomPercentage = true }; + using ProgressBar progressBar = new(filePaths.Count, message, options); + _ = Parallel.For(0, filePaths.Count, parallelOptions, (i, state) => + { + progressBar.Tick(); + AvailableParallelFor(propertyConfiguration, configuration, dFace, results, filePaths[i]); + }); + return results; + } + +} \ No newline at end of file diff --git a/Map/Models/Stateless/MapLogic.cs b/Map/Models/Stateless/MapLogic.cs index e93803b..2280705 100644 --- a/Map/Models/Stateless/MapLogic.cs +++ b/Map/Models/Stateless/MapLogic.cs @@ -438,7 +438,7 @@ internal abstract class MapLogic return result; } - private static List GetMappedFiles(Shared.Models.Properties.IPropertyConfiguration propertyConfiguration, Configuration configuration, ReadOnlyCollection personContainers, List records) + internal static List GetMappedFiles(Shared.Models.Properties.IPropertyConfiguration propertyConfiguration, Configuration configuration, ReadOnlyCollection personContainers, List records) { List results = []; long personKey; @@ -540,10 +540,13 @@ internal abstract class MapLogic exifDirectory, mappedFile.DirectoryNumber, personDisplayDirectoryName, + null, + null, mappedFile.FilePath, fromDistanceContent, id.Value, null, + null, mappedFile.PersonKey, rectangle, wholePercentages.Value)); @@ -561,6 +564,8 @@ internal abstract class MapLogic Dictionary distinct = []; foreach (LocationContainer locationContainer in locationContainers) { + if (locationContainer.PersonKey is null) + continue; key = string.Concat(locationContainer.PersonKey, locationContainer.Id); if (distinct.TryGetValue(key, out item)) { @@ -576,7 +581,7 @@ internal abstract class MapLogic } delete.Add(item.FilePath); delete.Add(locationContainer.FilePath); - duplicates.Add(new(locationContainer.PersonKey, locationContainer.Id, locationContainer.FilePath, percent)); + duplicates.Add(new(locationContainer.PersonKey.Value, locationContainer.Id, locationContainer.FilePath, percent)); continue; } distinct.Add(key, new(locationContainer.FilePath, locationContainer.WholePercentages)); @@ -990,6 +995,22 @@ internal abstract class MapLogic return result; } + internal static string GetFacePartsDirectoryX(Shared.Models.Properties.IPropertyConfiguration propertyConfiguration, string d2FacePartsContentDirectory, FilePath filePath) + { + string result; + (string directoryName, _) = IPath.GetDirectoryNameAndIndex(propertyConfiguration, filePath); + result = Path.Combine(d2FacePartsContentDirectory, directoryName, filePath.NameWithoutExtension); + return result; + } + + internal static string GetResizeContentDirectory(Shared.Models.Properties.IPropertyConfiguration propertyConfiguration, string cContentDirectory, FilePath filePath) + { + string result; + (string directoryName, _) = IPath.GetDirectoryNameAndIndex(propertyConfiguration, filePath); + result = Path.Combine(cContentDirectory, directoryName); + return result; + } + internal static SaveContainer GetDebugSaveContainer(SortingContainer sortingContainer, string directory, Mapping keyMapping) { SaveContainer result; @@ -1171,29 +1192,29 @@ internal abstract class MapLogic return results; } - internal static (string, bool, bool) Get(int? useFiltersCounter, bool saveIndividually, bool sortingContainersAny, string forceSingleImageHumanized, int? distancePermyriad, Mapping mapping) + internal static (string, bool, bool) Get(int? useFiltersCounter, bool saveIndividually, bool sortingContainersAny, string forceSingleImageHumanized, int? distancePermyriad, int? by, string? displayDirectoryName) { - string by; + string byValue; bool isByMapping; bool isBySorting; - if (mapping.By is null) + if (by is null) { isByMapping = false; isBySorting = !sortingContainersAny; - by = $"{nameof(Shared.Models.Stateless.IMapLogic.Mapping)}Null"; + byValue = $"{nameof(Shared.Models.Stateless.IMapLogic.Mapping)}Null"; } else { - isByMapping = mapping.By == Shared.Models.Stateless.IMapLogic.Mapping; - isBySorting = mapping.By == Shared.Models.Stateless.IMapLogic.Sorting; - bool isDefaultName = mapping.MappingFromPerson is not null && IPerson.IsDefaultName(mapping.MappingFromPerson.DisplayDirectoryName); - if (isBySorting && mapping.MappingFromPerson is null) - by = saveIndividually ? nameof(Shared.Models.Stateless.IMapLogic.Individually) : $"{nameof(Shared.Models.Stateless.IMapLogic.Sorting)} Without Person{(distancePermyriad < 2000 ? "-A" : "-Z")}"; + isByMapping = by == Shared.Models.Stateless.IMapLogic.Mapping; + isBySorting = by == Shared.Models.Stateless.IMapLogic.Sorting; + bool isDefaultName = displayDirectoryName is not null && IPerson.IsDefaultName(displayDirectoryName); + if (isBySorting && displayDirectoryName is null) + byValue = saveIndividually ? nameof(Shared.Models.Stateless.IMapLogic.Individually) : $"{nameof(Shared.Models.Stateless.IMapLogic.Sorting)} Without Person{(distancePermyriad < 2000 ? "-A" : "-Z")}"; else if (isBySorting && useFiltersCounter.HasValue) - by = $"{nameof(Shared.Models.Stateless.IMapLogic.Sorting)}{(!isDefaultName ? "-A" : "-Z")} Modified Filters - {useFiltersCounter.Value}"; + byValue = $"{nameof(Shared.Models.Stateless.IMapLogic.Sorting)}{(!isDefaultName ? "-A" : "-Z")} Modified Filters - {useFiltersCounter.Value}"; else { - by = $"{mapping.By.Value switch + byValue = $"{by.Value switch { Shared.Models.Stateless.IMapLogic.Mapping => nameof(Shared.Models.Stateless.IMapLogic.Mapping), Shared.Models.Stateless.IMapLogic.Sorting => saveIndividually ? nameof(Shared.Models.Stateless.IMapLogic.Individually) : nameof(Shared.Models.Stateless.IMapLogic.Sorting), @@ -1202,9 +1223,15 @@ internal abstract class MapLogic }}{(!isDefaultName ? "-A" : "-Z")}"; } } - return new(by, isByMapping, isBySorting); + return new(byValue, isByMapping, isBySorting); } + internal static (string, bool, bool) Get(int? useFiltersCounter, bool saveIndividually, bool sortingContainersAny, string forceSingleImageHumanized, int? distancePermyriad, long? personKey, string? displayDirectoryName) => + Get(useFiltersCounter, saveIndividually, sortingContainersAny, forceSingleImageHumanized, distancePermyriad, personKey is null ? null : Shared.Models.Stateless.IMapLogic.Mapping, displayDirectoryName); + + internal static (string, bool, bool) Get(int? useFiltersCounter, bool saveIndividually, bool sortingContainersAny, string forceSingleImageHumanized, int? distancePermyriad, Mapping mapping) => + Get(useFiltersCounter, saveIndividually, sortingContainersAny, forceSingleImageHumanized, distancePermyriad, mapping.By, mapping.MappingFromPerson?.DisplayDirectoryName); + internal static void CheckCollection(Shared.Models.Properties.IPropertyConfiguration propertyConfiguration, string? rootDirectoryParent) { string json; @@ -1272,7 +1299,7 @@ internal abstract class MapLogic return new(results); } - internal static bool? CanReMap(long[] jLinkResolvedPersonKeys, ReadOnlyDictionary>? wholePercentagesToPersonContainers, MappingFromLocation mappingFromLocation) + internal static bool? CanReMap(long[] jLinkResolvedPersonKeys, ReadOnlyDictionary>? wholePercentagesToPersonContainers, int wholePercentages) { bool? result; ReadOnlyCollection? personContainers; @@ -1280,7 +1307,7 @@ internal abstract class MapLogic result = null; else { - if (!wholePercentagesToPersonContainers.TryGetValue(mappingFromLocation.WholePercentages, out personContainers)) + if (!wholePercentagesToPersonContainers.TryGetValue(wholePercentages, out personContainers)) result = null; else { diff --git a/Map/Models/Stateless/Methods/IMapLogic.cs b/Map/Models/Stateless/Methods/IMapLogic.cs index 6a2a7df..ab363b0 100644 --- a/Map/Models/Stateless/Methods/IMapLogic.cs +++ b/Map/Models/Stateless/Methods/IMapLogic.cs @@ -1,5 +1,6 @@ using System.Collections.ObjectModel; using View_by_Distance.Shared.Models; +using View_by_Distance.Shared.Models.Stateless.Methods; namespace View_by_Distance.Map.Models.Stateless.Methods; @@ -49,11 +50,26 @@ public interface IMapLogic bool? TestStatic_CanReMap(long[] jLinkResolvedPersonKeys, ReadOnlyDictionary>? wholePercentagesToPersonContainers, MappingFromLocation mappingFromLocation) => CanReMap(jLinkResolvedPersonKeys, wholePercentagesToPersonContainers, mappingFromLocation); static bool? CanReMap(long[] jLinkResolvedPersonKeys, ReadOnlyDictionary>? wholePercentagesToPersonContainers, MappingFromLocation mappingFromLocation) => - MapLogic.CanReMap(jLinkResolvedPersonKeys, wholePercentagesToPersonContainers, mappingFromLocation); + MapLogic.CanReMap(jLinkResolvedPersonKeys, wholePercentagesToPersonContainers, mappingFromLocation.WholePercentages); + + bool? TestStatic_CanReMap(long[] jLinkResolvedPersonKeys, ReadOnlyDictionary>? wholePercentagesToPersonContainers, int wholePercentages) => + CanReMap(jLinkResolvedPersonKeys, wholePercentagesToPersonContainers, wholePercentages); + static bool? CanReMap(long[] jLinkResolvedPersonKeys, ReadOnlyDictionary>? wholePercentagesToPersonContainers, int wholePercentages) => + MapLogic.CanReMap(jLinkResolvedPersonKeys, wholePercentagesToPersonContainers, wholePercentages); string TestStatic_GetDecade(MappingFromItem mappingFromItem) => GetDecade(mappingFromItem); static string GetDecade(MappingFromItem mappingFromItem) => DecadeLogic.GetDecade(mappingFromItem, null); + ReadOnlyDictionary> TestStatic_GetMappedFiles(int maxDegreeOfParallelism, Property.Models.Configuration propertyConfiguration, Configuration configuration, long ticks, ReadOnlyCollection personContainers, string a2PeopleSingletonDirectory, string eDistanceContentDirectory) => + GetMapped(maxDegreeOfParallelism, propertyConfiguration, configuration, ticks, personContainers, a2PeopleSingletonDirectory, eDistanceContentDirectory); + static ReadOnlyDictionary> GetMapped(int maxDegreeOfParallelism, Property.Models.Configuration propertyConfiguration, Configuration configuration, long ticks, ReadOnlyCollection personContainers, string a2PeopleSingletonDirectory, string eDistanceContentDirectory) => + FaceFileLogic.GetMapped(maxDegreeOfParallelism, propertyConfiguration, configuration, ticks, personContainers, a2PeopleSingletonDirectory, eDistanceContentDirectory); + + List TestStatic_GetAvailable(int maxDegreeOfParallelism, Property.Models.Configuration propertyConfiguration, Configuration configuration, IFaceD dFace, long ticks, string dFacesContentDirectory, ReadOnlyCollection filePaths) => + GetAvailable(maxDegreeOfParallelism, propertyConfiguration, configuration, dFace, ticks, dFacesContentDirectory, filePaths); + static List GetAvailable(int maxDegreeOfParallelism, Property.Models.Configuration propertyConfiguration, Configuration configuration, IFaceD dFace, long ticks, string dFacesContentDirectory, ReadOnlyCollection filePaths) => + FaceFileLogic.GetAvailable(maxDegreeOfParallelism, propertyConfiguration, configuration, dFace, ticks, dFacesContentDirectory, filePaths); + } \ No newline at end of file diff --git a/Map/Models/Stateless/RelationLogic.cs b/Map/Models/Stateless/RelationLogic.cs index b9cdd57..ee5ae47 100644 --- a/Map/Models/Stateless/RelationLogic.cs +++ b/Map/Models/Stateless/RelationLogic.cs @@ -17,14 +17,16 @@ internal abstract class RelationLogic Dictionary>> personKeyTo = []; foreach (LocationContainer locationContainer in locationContainers) { + if (locationContainer.PersonKey is null) + continue; if (!locationContainer.FromDistanceContent) continue; if (!locationContainer.FilePath.FullName.Contains(configuration.LocationContainerDirectoryPattern)) continue; - if (!personKeyTo.TryGetValue(locationContainer.PersonKey, out yearTo)) + if (!personKeyTo.TryGetValue(locationContainer.PersonKey.Value, out yearTo)) { - personKeyTo.Add(locationContainer.PersonKey, []); - if (!personKeyTo.TryGetValue(locationContainer.PersonKey, out yearTo)) + personKeyTo.Add(locationContainer.PersonKey.Value, []); + if (!personKeyTo.TryGetValue(locationContainer.PersonKey.Value, out yearTo)) throw new Exception(); } if (!yearTo.TryGetValue(locationContainer.CreationDateOnly.Year, out collection)) @@ -45,6 +47,7 @@ internal abstract class RelationLogic int lastIndex; List years = []; List indices = []; + LocationContainer locationContainer; List<(int Index, int Year)> sort = []; List collection = []; KeyValuePair> keyValue; @@ -76,7 +79,10 @@ internal abstract class RelationLogic key = $"{years.Min()}-{keyValue.Key}"; if (collection.Count == 0) continue; - results.Add(new(key, collection[0].PersonKey, new(collection))); + locationContainer = collection[0]; + if (locationContainer.PersonKey is null) + continue; + results.Add(new(key, locationContainer.PersonKey.Value, new(collection))); collection = []; years.Clear(); } diff --git a/Metadata/Models/Stateless/Face.cs b/Metadata/Models/Stateless/Face.cs index 30c77ee..8cb08b1 100644 --- a/Metadata/Models/Stateless/Face.cs +++ b/Metadata/Models/Stateless/Face.cs @@ -35,6 +35,17 @@ internal static class Face result = pngDirectory.TextualData[artist.Length..]; break; } + if (result is null) + { + const string author = "Author:"; + foreach (PngDirectory pngDirectory in pngDirectories) + { + if (pngDirectory.TextualData is null || !pngDirectory.TextualData.StartsWith(author)) + continue; + result = pngDirectory.TextualData[author.Length..]; + break; + } + } } return result; } diff --git a/Offset-Date-Time-Original/OffsetDateTimeOriginal.cs b/Offset-Date-Time-Original/OffsetDateTimeOriginal.cs index 0b9c7dd..0181417 100644 --- a/Offset-Date-Time-Original/OffsetDateTimeOriginal.cs +++ b/Offset-Date-Time-Original/OffsetDateTimeOriginal.cs @@ -112,7 +112,7 @@ public class OffsetDateTimeOriginal string checkFile; PropertyItem? propertyItem; string? ticksDirectory = null; - int dateTimeOriginal = MetadataExtractor.Formats.Exif.ExifDirectoryBase.TagDateTimeOriginal; + const int dateTimeOriginal = MetadataExtractor.Formats.Exif.ExifDirectoryBase.TagDateTimeOriginal; // 36867 for (int i = 0; i < int.MaxValue; i++) { ticksDirectory = Path.Combine(sourceDirectory, ticks.ToString()); diff --git a/Shared/Models/FaceFile.cs b/Shared/Models/FaceFile.cs index c59a536..ee46397 100644 --- a/Shared/Models/FaceFile.cs +++ b/Shared/Models/FaceFile.cs @@ -2,7 +2,9 @@ using System.Text.Json.Serialization; namespace View_by_Distance.Shared.Models; -public record FaceFile(DateTime DateTime, +public record FaceFile(int? AreaPermyriad, + int? ConfidencePercent, + DateTime DateTime, string? DMS, Dictionary? FaceParts, Location? Location, diff --git a/Shared/Models/LocationContainer.cs b/Shared/Models/LocationContainer.cs index 9b711e9..8918861 100644 --- a/Shared/Models/LocationContainer.cs +++ b/Shared/Models/LocationContainer.cs @@ -5,11 +5,57 @@ namespace View_by_Distance.Shared.Models; public record LocationContainer(DateOnly CreationDateOnly, ExifDirectory? ExifDirectory, int? DirectoryNumber, - string DisplayDirectoryName, + string? DisplayDirectoryName, + object? Encoding, + FaceFile? FaceFile, FilePath FilePath, bool FromDistanceContent, int Id, - Location? Location, - long PersonKey, + int? LengthPermyriad, + FilePath? LengthSource, + long? PersonKey, RectangleF? Rectangle, - int WholePercentages); \ No newline at end of file + int WholePercentages) +{ + + public static LocationContainer Get(LocationContainer locationContainer, object? encoding, bool keepExifDirectory) + { + LocationContainer result; + result = new(locationContainer.CreationDateOnly, + keepExifDirectory ? locationContainer.ExifDirectory : null, + locationContainer.DirectoryNumber, + locationContainer.DisplayDirectoryName, + encoding, + locationContainer.FaceFile, + locationContainer.FilePath, + locationContainer.FromDistanceContent, + locationContainer.Id, + locationContainer.LengthPermyriad, + locationContainer.LengthSource, + locationContainer.PersonKey, + locationContainer.Rectangle, + locationContainer.WholePercentages); + return result; + } + + public static LocationContainer Get(LocationContainer source, LocationContainer locationContainer, int lengthPermyriad, bool keepExifDirectory, bool keepEncoding) + { + LocationContainer result; + result = new(locationContainer.CreationDateOnly, + keepExifDirectory ? locationContainer.ExifDirectory : null, + locationContainer.DirectoryNumber, + locationContainer.DisplayDirectoryName, + keepEncoding ? locationContainer.Encoding : null, + locationContainer.FaceFile, + locationContainer.FilePath, + locationContainer.FromDistanceContent, + locationContainer.Id, + lengthPermyriad, + source.FilePath, + locationContainer.PersonKey, + locationContainer.Rectangle, + locationContainer.WholePercentages); + return result; + } + +} \ No newline at end of file diff --git a/Shared/Models/Stateless/Methods/IFaceD.cs b/Shared/Models/Stateless/Methods/IFaceD.cs index 0a02b10..f0963e6 100644 --- a/Shared/Models/Stateless/Methods/IFaceD.cs +++ b/Shared/Models/Stateless/Methods/IFaceD.cs @@ -4,6 +4,6 @@ public interface IFaceD { public string FileNameExtension { get; } - void ReSaveFace(ExifDirectory exifDirectory, LocationContainer locationContainer, Models.Face face); + void ReSaveFace(ExifDirectory exifDirectory, FilePath filePath, Models.Face face, bool mappedFile); } \ No newline at end of file diff --git a/Shared/Models/Stateless/Methods/ILocation.cs b/Shared/Models/Stateless/Methods/ILocation.cs index 607e438..8c27447 100644 --- a/Shared/Models/Stateless/Methods/ILocation.cs +++ b/Shared/Models/Stateless/Methods/ILocation.cs @@ -10,9 +10,9 @@ public interface ILocation static Models.Location? GetLocation(int height, Rectangle rectangle, int width) => Location.GetLocation(height, rectangle, width); - List TestStatic_FilterByIntersect(Models.Face[] faces, float rectangleIntersectMinimum, int wholePercentages) => + List TestStatic_FilterByIntersect(List faces, float rectangleIntersectMinimum, int wholePercentages) => FilterByIntersect(faces, rectangleIntersectMinimum, wholePercentages); - static List FilterByIntersect(Models.Face[] faces, float rectangleIntersectMinimum, int wholePercentages) => + static List FilterByIntersect(List faces, float rectangleIntersectMinimum, int wholePercentages) => Location.FilterByIntersect(faces, rectangleIntersectMinimum, wholePercentages); RectangleF? TestStatic_GetPercentagesRectangle(DatabaseFile databaseFile, Marker marker, Models.OutputResolution outputResolution) => diff --git a/Shared/Models/Stateless/Methods/Location.cs b/Shared/Models/Stateless/Methods/Location.cs index 0e1d2fe..2ee5801 100644 --- a/Shared/Models/Stateless/Methods/Location.cs +++ b/Shared/Models/Stateless/Methods/Location.cs @@ -310,7 +310,7 @@ internal abstract class Location return results; } - internal static List FilterByIntersect(Models.Face[] faces, float rectangleIntersectMinimum, int wholePercentages) + internal static List FilterByIntersect(List faces, float rectangleIntersectMinimum, int wholePercentages) { List results = []; float? percent;