From 326e579d5cc2f0b75f77a4ce5d82176083d5e458 Mon Sep 17 00:00:00 2001 From: Mike Phares Date: Sat, 31 Aug 2024 08:32:06 -0700 Subject: [PATCH] Runs but broken --- .editorconfig | 27 + Copy-Distinct/CopyDistinct.cs | 3 +- Distance/Models/DistanceLimits.cs | 2 + Distance/Models/_E_Distance.cs | 235 +++++-- Duplicate-Search/DuplicateSearch.cs | 11 +- Face/Models/_D_Face.cs | 223 ++++--- FaceParts/Models/_D2_FaceParts.cs | 24 +- FaceRecognitionDotNet/FaceRecognition.cs | 48 +- Instance/DlibDotNet.cs | 572 ++++++++++++------ Instance/Models/_F_Random.cs | 50 +- Map/Models/Configuration.cs | 4 +- Map/Models/MapLogic.cs | 268 ++++++-- Map/Models/Stateless/DecadeLogic.cs | 25 +- Map/Models/Stateless/DistanceLogic.cs | 23 +- Map/Models/Stateless/FaceFileLogic.cs | 212 +++++++ Map/Models/Stateless/LookForAbandonedLogic.cs | 9 +- Map/Models/Stateless/MapLogic.cs | 76 ++- Map/Models/Stateless/Methods/IMapLogic.cs | 60 +- Map/Models/Stateless/RelationLogic.cs | 18 +- PhotoPrism/Models/_F_PhotoPrism.cs | 4 +- Property/Models/A_Property.cs | 2 +- Rename/Rename.cs | 2 +- Shared/Models/Container.cs | 3 +- Shared/Models/FileHolder.cs | 51 +- Shared/Models/FilePath.cs | 9 +- Shared/Models/LocationContainer.cs | 54 +- Shared/Models/Methods/IDistance.cs | 2 +- Shared/Models/Methods/IDistanceLimits.cs | 1 + Shared/Models/Stateless/Methods/Container.cs | 112 ++-- Shared/Models/Stateless/Methods/IContainer.cs | 33 +- Shared/Models/Stateless/Methods/IDirectory.cs | 26 +- .../Models/Stateless/Methods/IDlibDotNet.cs | 8 + Shared/Models/Stateless/Methods/IFaceD.cs | 9 + .../Models/Stateless/Methods/IFileHolder.cs | 5 + Shared/Models/Stateless/Methods/IId.cs | 21 +- Shared/Models/Stateless/Methods/ILocation.cs | 12 +- .../Stateless/Methods/IPersonContainer.cs | 6 +- Shared/Models/Stateless/Methods/IProperty.cs | 4 +- Shared/Models/Stateless/Methods/Id.cs | 14 +- Shared/Models/Stateless/Methods/Location.cs | 23 +- .../Stateless/Methods/PersonContainer.cs | 4 +- Shared/Models/Stateless/Methods/Property.cs | 6 +- Shared/Models/Stateless/Methods/XDirectory.cs | 116 +++- 43 files changed, 1763 insertions(+), 654 deletions(-) create mode 100644 Map/Models/Stateless/FaceFileLogic.cs create mode 100644 Shared/Models/Stateless/Methods/IDlibDotNet.cs create mode 100644 Shared/Models/Stateless/Methods/IFaceD.cs diff --git a/.editorconfig b/.editorconfig index 27bc698..7410050 100644 --- a/.editorconfig +++ b/.editorconfig @@ -82,33 +82,60 @@ csharp_style_var_elsewhere = false:warning csharp_style_var_for_built_in_types = false:warning csharp_style_var_when_type_is_apparent = false:warning csharp_using_directive_placement = outside_namespace +dotnet_analyzer_diagnostic.category-Design.severity = error +dotnet_analyzer_diagnostic.category-Documentation.severity = error +dotnet_analyzer_diagnostic.category-Globalization.severity = none +dotnet_analyzer_diagnostic.category-Interoperability.severity = error +dotnet_analyzer_diagnostic.category-Maintainability.severity = error +dotnet_analyzer_diagnostic.category-Naming.severity = none +dotnet_analyzer_diagnostic.category-Performance.severity = none +dotnet_analyzer_diagnostic.category-Reliability.severity = error +dotnet_analyzer_diagnostic.category-Security.severity = error +dotnet_analyzer_diagnostic.category-SingleFile.severity = error +dotnet_analyzer_diagnostic.category-Style.severity = error +dotnet_analyzer_diagnostic.category-Usage.severity = error dotnet_code_quality_unused_parameters = all dotnet_code_quality_unused_parameters = non_public # IDE0060: Remove unused parameter dotnet_code_quality.CAXXXX.api_surface = private, internal +dotnet_diagnostic.CA1001.severity = none # CA1001: Types that own disposable fields should be disposable +dotnet_diagnostic.CA1051.severity = none # CA1051: Do not declare visible instance fields dotnet_diagnostic.CA1511.severity = warning # CA1511: Use 'ArgumentException.ThrowIfNullOrEmpty' instead of explicitly throwing a new exception instance +dotnet_diagnostic.CA1511.severity = warning # CA1511: Use 'ArgumentException.ThrowIfNullOrEmpty' instead of explicitly throwing a new exception instance +dotnet_diagnostic.CA1513.severity = warning # Use 'ObjectDisposedException.ThrowIf' instead of explicitly throwing a new exception instance +dotnet_diagnostic.CA1816.severity = none # CA1816: Call GC.SuppressFinalize correctly dotnet_diagnostic.CA1825.severity = warning # CA1823: Avoid zero-length array allocations dotnet_diagnostic.CA1829.severity = warning # CA1829: Use Length/Count property instead of Count() when available dotnet_diagnostic.CA1834.severity = warning # CA1834: Consider using 'StringBuilder.Append(char)' when applicable +dotnet_diagnostic.CA1854.severity = warning # CA1854: Prefer a 'TryGetValue' call over a Dictionary indexer access guarded by a 'ContainsKey' check to avoid double lookup dotnet_diagnostic.CA1860.severity = warning # CA1860: Prefer comparing 'Count' to 0 rather than using 'Any()', both for clarity and for performance +dotnet_diagnostic.CA1861.severity = none # CA1861: Prefer 'static readonly' fields over constant array arguments dotnet_diagnostic.CA1862.severity = warning # CA1862: Prefer using 'string.Equals(string, StringComparison)' to perform a case-insensitive comparison, but keep in mind that this might cause subtle changes in behavior, so make sure to conduct thorough testing after applying the suggestion, or if culturally sensitive comparison is not required, consider using 'StringComparison.OrdinalIgnoreCase' +dotnet_diagnostic.CA1866.severity = none # CA1866: Use 'string.EndsWith(char)' instead of 'string.EndsWith(string)' when you have a string with a single char dotnet_diagnostic.CA1869.severity = none # CA1869: Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. +dotnet_diagnostic.CA2201.severity = none # CA2201: Exception type System.NullReferenceException is reserved by the runtime dotnet_diagnostic.CA2254.severity = none # CA2254: The logging message template should not vary between calls to 'LoggerExtensions.LogInformation(ILogger, string?, params object?[])' dotnet_diagnostic.IDE0001.severity = warning # IDE0001: Simplify name dotnet_diagnostic.IDE0002.severity = warning # Simplify (member access) - System.Version.Equals("1", "2"); Version.Equals("1", "2"); dotnet_diagnostic.IDE0004.severity = warning # IDE0004: Cast is redundant. dotnet_diagnostic.IDE0005.severity = warning # Using directive is unnecessary +dotnet_diagnostic.IDE0010.severity = none # Add missing cases to switch statement (IDE0010) dotnet_diagnostic.IDE0028.severity = warning # IDE0028: Collection initialization can be simplified dotnet_diagnostic.IDE0031.severity = warning # Use null propagation (IDE0031) dotnet_diagnostic.IDE0047.severity = warning # IDE0047: Parentheses can be removed +dotnet_diagnostic.IDE0048.severity = none # Parentheses preferences (IDE0047 and IDE0048) dotnet_diagnostic.IDE0049.severity = warning # Use language keywords instead of framework type names for type references (IDE0049) +dotnet_diagnostic.IDE0051.severity = error # Private member '' is unused [, ] dotnet_diagnostic.IDE0058.severity = warning # IDE0058: Expression value is never used dotnet_diagnostic.IDE0060.severity = warning # IDE0060: Remove unused parameter dotnet_diagnostic.IDE0074.severity = warning # IDE0074: Use compound assignment +dotnet_diagnostic.IDE0130.severity = none # Namespace does not match folder structure (IDE0130) dotnet_diagnostic.IDE0200.severity = warning # IDE0200: Lambda expression can be removed [Map] +dotnet_diagnostic.IDE0230.severity = warning # IDE0230: Use UTF-8 string literal dotnet_diagnostic.IDE0290.severity = none # Use primary constructor [Distance]csharp(IDE0290) dotnet_diagnostic.IDE0300.severity = warning # IDE0300: Collection initialization can be simplified dotnet_diagnostic.IDE0301.severity = warning #IDE0301: Collection initialization can be simplified dotnet_diagnostic.IDE0305.severity = none # IDE0305: Collection initialization can be simplified +dotnet_diagnostic.JSON002.severity = warning # JSON002: Probable JSON string detected dotnet_naming_rule.abstract_method_should_be_pascal_case.severity = warning dotnet_naming_rule.abstract_method_should_be_pascal_case.style = pascal_case dotnet_naming_rule.abstract_method_should_be_pascal_case.symbols = abstract_method diff --git a/Copy-Distinct/CopyDistinct.cs b/Copy-Distinct/CopyDistinct.cs index 769a6d5..eea9737 100644 --- a/Copy-Distinct/CopyDistinct.cs +++ b/Copy-Distinct/CopyDistinct.cs @@ -180,7 +180,8 @@ public class CopyDistinct progressBar = new(count, message, options); string key = string.IsNullOrEmpty(_AppSettings.ResultDirectoryKey) ? _PropertyConfiguration.ResultAllInOne : _AppSettings.ResultDirectoryKey; string[] directories = _FileGroups[key]; - (distinctDirectories, toDoCollection) = IDirectory.GetToDoCollection(_PropertyConfiguration, _AppSettings.CopyDuplicates, _AppSettings.IfCanUseId, filesCollection, directories, () => progressBar.Tick()); + ReadOnlyCollection> filePathsCollection = IDirectory.GetFilePathCollections(_Configuration.PropertyConfiguration, filesCollection); + (distinctDirectories, toDoCollection) = IDirectory.GetToDoCollection(_PropertyConfiguration, _AppSettings.CopyDuplicates, _AppSettings.IfCanUseId, filePathsCollection, directories, () => progressBar.Tick()); progressBar.Dispose(); } foreach (string distinctDirectory in distinctDirectories) diff --git a/Distance/Models/DistanceLimits.cs b/Distance/Models/DistanceLimits.cs index 1632175..0c858e4 100644 --- a/Distance/Models/DistanceLimits.cs +++ b/Distance/Models/DistanceLimits.cs @@ -14,6 +14,7 @@ public class DistanceLimits : IDistanceLimits public double FaceDistancePermyriad { init; get; } public int SortingMaximumPerFaceShouldBeHigh { init; get; } public bool RangeDaysDeltaTargetLessThenUpper { init; get; } + public double RangeDistanceToleranceUpperLimit { init; get; } public DistanceLimits(int faceAreaPermyriad, int faceConfidencePercent, @@ -25,6 +26,7 @@ public class DistanceLimits : IDistanceLimits int sortingMaximumPerFaceShouldBeHigh, int? useFiltersCounter = null) { + RangeDistanceToleranceUpperLimit = rangeDistanceTolerance[2]; SortingMaximumPerFaceShouldBeHigh = sortingMaximumPerFaceShouldBeHigh; RangeDaysDeltaTargetLessThenUpper = rangeDaysDeltaTolerance[1] > rangeDaysDeltaTolerance[2]; if (useFiltersCounter is null) diff --git a/Distance/Models/_E_Distance.cs b/Distance/Models/_E_Distance.cs index e830ec8..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; @@ -50,6 +53,7 @@ public partial class E_Distance : IDistance private FaceDistanceContainer[] GetFaceDistanceContainers(MappingFromItem mappingFromItem, List intersectFaces) { FaceDistanceContainer[] results; + DateTime dateTime; int wholePercentages; int confidencePercent; FaceDistance faceDistance; @@ -61,14 +65,15 @@ public partial class E_Distance : IDistance throw new NotSupportedException(); if (face.Mapping?.MappingFromFilterPost is null) throw new NotSupportedException(); + dateTime = mappingFromItem.GetDateTimeOriginalThenMinimumDateTime(); 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); + faceDistance = new(confidencePercent, dateTime, 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); + faceDistance = new(confidencePercent, dateTime, faceEncoding, face.Mapping?.MappingFromFilterPost, mappingFromItem.Id, mappingFromItem.IsWrongYear, wholePercentages); lock (intersectFaces) face.SetFaceDistance(faceDistance); } @@ -91,7 +96,7 @@ public partial class E_Distance : IDistance return new(faceDistanceEncodings); } - private List<(Face Face, double? Length)> GetValues(MappingFromItem mappingFromItem, List intersectFaces, Shared.Models.FaceEncoding modelsFaceEncoding) + private List<(Face Face, double? Length)> GetValues(IDistanceLimits distanceLimits, MappingFromItem mappingFromItem, List intersectFaces, Shared.Models.FaceEncoding modelsFaceEncoding) { List<(Face Face, double? Length)> results = []; Face face; @@ -112,6 +117,8 @@ public partial class E_Distance : IDistance { face = intersectFaces[i]; faceDistanceLength = faceDistanceLengths[i]; + if (faceDistanceLength.Length is null || faceDistanceLength.Length > distanceLimits.RangeDistanceToleranceUpperLimit) + continue; if (faceDistanceLength.Length is null) throw new NotSupportedException(); results.Add(new(face, faceDistanceLength.Length.Value)); @@ -119,10 +126,10 @@ public partial class E_Distance : IDistance return results; } - private (Face, double?)[] GetClosestFaceByDistanceIgnoringTolerance(MappingFromItem mappingFromItem, List intersectFaces, Shared.Models.FaceEncoding modelsFaceEncoding) + private (Face, double?)[] GetClosestFaceByDistanceIgnoringTolerance(IDistanceLimits distanceLimits, MappingFromItem mappingFromItem, List intersectFaces, Shared.Models.FaceEncoding modelsFaceEncoding) { (Face, double?)[] results; - List<(Face Face, double? Length)> collection = GetValues(mappingFromItem, intersectFaces, modelsFaceEncoding); + List<(Face Face, double? Length)> collection = GetValues(distanceLimits, mappingFromItem, intersectFaces, modelsFaceEncoding); results = (from l in collection where l.Length < _RangeDistanceToleranceAverage orderby l.Length select l).Take(1).ToArray(); if (results.Length > 0) { @@ -132,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; @@ -165,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; @@ -201,17 +208,14 @@ public partial class E_Distance : IDistance } } - public void LookForMatchFacesAndPossiblyRename(string facesFileNameExtension, FilePath filePath, MappingFromItem mappingFromItem, 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; FileInfo? fileInfo; List intersectFaces; - List<(Face, double?)> checkFaces = []; 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(); + List<(Face Face, double? Distance)> checkFaces = []; foreach (LocationContainer locationContainer in locationContainers) { if (_Renamed.Contains(locationContainer.FilePath.FullName)) @@ -232,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)) @@ -242,11 +246,11 @@ 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(mappingFromItem, intersectFaces, modelsFaceEncoding)); + checkFaces.AddRange(GetClosestFaceByDistanceIgnoringTolerance(distanceLimits, mappingFromItem, intersectFaces, modelsFaceEncoding)); } } if (checkFaces.Count == 0) @@ -261,7 +265,7 @@ public partial class E_Distance : IDistance MoveUnableToMatch(locationContainer.FilePath); continue; } - fileInfo = CheckFileThenGetFileInfo(facesFileNameExtension, filePath, mappingFromItem, locationContainer.FilePath.FullName, checkFaces); + fileInfo = CheckFileThenGetFileInfo(dFace.FileNameExtension, filePath, mappingFromItem, locationContainer.FilePath.FullName, checkFaces); if (fileInfo is not null) { if (_DistanceRenameToMatch && fileInfo is not null) @@ -275,6 +279,15 @@ public partial class E_Distance : IDistance } continue; } + if (overrideForFaceImages) + { + json = Metadata.Models.Stateless.Methods.IMetadata.GetOutputResolution(locationContainer.ExifDirectory); + 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)) { lock (_AllMappedFaceFiles) @@ -322,10 +335,88 @@ public partial class E_Distance : IDistance File.WriteAllLines(eDistanceContentFileName, results); } - public static void PreFilterSetFaceDistances(int maxDegreeOfParallelism, long ticks, ReadOnlyCollection distinctFilteredFaces) + 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 distinctFilteredFaces) + foreach (Face face in distinctValidImageFaces) { if (face.Mapping?.MappingFromFilterPre is null) throw new NotSupportedException(); @@ -335,7 +426,10 @@ public partial class E_Distance : IDistance continue; if (face.Mapping.MappingFromFilterPre.IsFocusRelativePath is not null && !face.Mapping.MappingFromFilterPre.IsFocusRelativePath.Value) continue; - if (face.FaceEncoding is not null && face.FaceDistance?.Encoding is not null && face.FaceDistance.Encoding is FaceRecognitionDotNet.FaceEncoding _) + if (!configuration.ReMap && face.Mapping.MappingFromPerson is not null) + continue; + if (!configuration.ReMap && face.FaceEncoding is not null && face.FaceDistance?.Encoding is not null && face.FaceDistance.Encoding is FaceRecognitionDotNet.FaceEncoding) + // throw new NotSupportedException($"{face.FaceEncoding} should not be null!"); continue; faces.Add(face); } @@ -352,18 +446,19 @@ public partial class E_Distance : IDistance 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); + DateTime dateTime = face.Mapping.MappingFromItem.GetDateTimeOriginalThenMinimumDateTime(); + FaceDistance faceDistance = new(face.Mapping.MappingFromLocation.ConfidencePercent, dateTime, 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) + private static List GetSortingContainers(Configuration mapConfiguration, IDistanceLimits distanceLimits, Face face, FaceDistance faceDistanceEncoding, List sortingCollection) { List results = []; - SortingContainer sortingContainer; int days = 0, distance = 0; - Sorting[] collection = Shared.Models.Stateless.Methods.ISorting.Sort(sortingCollection); + SortingContainer sortingContainer; + Sorting[] collection = ISorting.Sort(sortingCollection); foreach (Sorting sorting in collection) { if (face.Mapping?.MappingFromLocation is null || faceDistanceEncoding.WholePercentages is null) @@ -389,7 +484,7 @@ public partial class E_Distance : IDistance return results; } - private static List GetSortingCollection(Map.Models.MapLogic mapLogic, 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); @@ -397,19 +492,21 @@ public partial class E_Distance : IDistance return results; } - public static ReadOnlyCollection GetFaceDistanceContainers(ReadOnlyCollection distinctFilteredFaces) + public static ReadOnlyCollection GetFaceDistanceContainers(ReadOnlyCollection distinctValidImageFaces) { ReadOnlyCollection results; + DateTime dateTime; FaceDistance faceDistance; FaceDistanceContainer faceDistanceContainer; List collection = []; - foreach (Face face in distinctFilteredFaces) + foreach (Face face in distinctValidImageFaces) { 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); + dateTime = face.Mapping.MappingFromItem.GetDateTimeOriginalThenMinimumDateTime(); + faceDistance = new(face.Mapping.MappingFromLocation.ConfidencePercent, dateTime, faceEncoding, face.Mapping.MappingFromFilterPost, face.Mapping.MappingFromItem.Id, face.Mapping.MappingFromItem.IsWrongYear, face.Mapping.MappingFromLocation.WholePercentages); faceDistanceContainer = new(face, faceDistance); collection.Add(faceDistanceContainer); } @@ -417,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) @@ -446,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); @@ -470,13 +622,13 @@ 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); } - private static ReadOnlyCollection GetRelationCollections(int faceDistancePermyriad, int locationContainerDistanceTake, float distanceTolerance, List records) + private static ReadOnlyCollection GetRelationCollections(IDistanceLimits distanceLimits, int faceDistancePermyriad, int locationContainerDistanceTake, float distanceTolerance, List records) { List results = []; string fileName; @@ -496,6 +648,7 @@ public partial class E_Distance : IDistance foreach (Record record in records) { mappedRelations = []; + FaceDistance faceDistanceLength; fileHolder = Shared.Models.Stateless.Methods.IFileHolder.Get(record.FilePath.FullName); if (files.Count > 1) { @@ -508,10 +661,12 @@ public partial class E_Distance : IDistance fileName = Path.GetFileName(files[i]); if (fileName == fileHolder.Name) continue; - FaceDistance faceDistance = faceDistanceLengths[i]; - if (faceDistance.Length is null || faceDistance.Length.Value > distanceTolerance) + faceDistanceLength = faceDistanceLengths[i]; + if (faceDistanceLength.Length is null || faceDistanceLength.Length > distanceLimits.RangeDistanceToleranceUpperLimit) continue; - distancePermyriad = (int)(faceDistance.Length.Value * faceDistancePermyriad); + if (faceDistanceLength.Length is null || faceDistanceLength.Length.Value > distanceTolerance) + continue; + distancePermyriad = (int)(faceDistanceLength.Length.Value * faceDistancePermyriad); mappedRelations.Add(new(distancePermyriad, files[i])); } } @@ -521,7 +676,7 @@ public partial class E_Distance : IDistance return new(results); } - ReadOnlyCollection IDistance.GetRelationContainers(int faceDistancePermyriad, int locationContainerDistanceTake, float locationContainerDistanceTolerance, ReadOnlyCollection locationContainers) + ReadOnlyCollection IDistance.GetRelationContainers(IDistanceLimits distanceLimits, int faceDistancePermyriad, int locationContainerDistanceTake, float locationContainerDistanceTolerance, ReadOnlyCollection locationContainers) { ReadOnlyCollection result; string? json; @@ -539,7 +694,7 @@ public partial class E_Distance : IDistance faceRecognitionDotNetFaceEncoding = FaceRecognition.LoadFaceEncoding(modelsFaceEncoding.RawEncoding); records.Add(new(locationContainer.FilePath, faceRecognitionDotNetFaceEncoding)); } - result = GetRelationCollections(faceDistancePermyriad, locationContainerDistanceTake, locationContainerDistanceTolerance, records); + result = GetRelationCollections(distanceLimits, faceDistancePermyriad, locationContainerDistanceTake, locationContainerDistanceTolerance, records); return result; } diff --git a/Duplicate-Search/DuplicateSearch.cs b/Duplicate-Search/DuplicateSearch.cs index 0bdd6b2..6272518 100644 --- a/Duplicate-Search/DuplicateSearch.cs +++ b/Duplicate-Search/DuplicateSearch.cs @@ -2,6 +2,7 @@ using Microsoft.Extensions.Logging; using Phares.Shared; using ShellProgressBar; +using System.Collections.ObjectModel; using System.Text; using System.Text.Json; using View_by_Distance.Duplicate.Search.Models; @@ -145,11 +146,11 @@ public class DuplicateSearch Dictionary> results = []; string directory; const int zero = 0; - Item[] filteredItems; FileHolder resizedFileHolder; DateTime[] containerDateTimes; MappingFromItem? mappingFromItem; List? collection; + ReadOnlyCollection validImageItems; const string duplicates = "-Duplicate(s)"; if (containers.Length != 0) { @@ -162,11 +163,11 @@ public class DuplicateSearch continue; if (!argZeroIsConfigurationRootDirectory && !container.SourceDirectory.StartsWith(argZero)) continue; - filteredItems = Shared.Models.Stateless.Methods.IContainer.GetFilterItems(configuration, container); - if (filteredItems.Length == 0) + validImageItems = Shared.Models.Stateless.Methods.IContainer.GetValidImageItems(configuration, container); + if (validImageItems.Count == 0) continue; - containerDateTimes = Shared.Models.Stateless.Methods.IContainer.GetContainerDateTimes(filteredItems); - foreach (Item item in filteredItems) + containerDateTimes = Shared.Models.Stateless.Methods.IContainer.GetContainerDateTimes(validImageItems); + foreach (Item item in validImageItems) { if (item.Property?.Id is null) { diff --git a/Face/Models/_D_Face.cs b/Face/Models/_D_Face.cs index 33e4970..c6df1bc 100644 --- a/Face/Models/_D_Face.cs +++ b/Face/Models/_D_Face.cs @@ -6,19 +6,20 @@ 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.Property.Models.Stateless; using View_by_Distance.Resize.Models; using View_by_Distance.Shared.Models; using View_by_Distance.Shared.Models.Properties; using View_by_Distance.Shared.Models.Stateless; +using View_by_Distance.Shared.Models.Stateless.Methods; namespace View_by_Distance.Face.Models; /// // List /// -public class D_Face +public class D_Face : IFaceD { protected readonly string _FileNameExtension; @@ -149,51 +150,66 @@ public class D_Face #pragma warning disable CA1416 - private void SaveFaces(FileHolder resizedFileHolder, 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; Bitmap bitmap; short type = 2; + FaceFile faceFile; Graphics graphics; Location? location; Rectangle rectangle; - string locationJson; + string faceFileJson; string faceEncodingJson; PropertyItem? propertyItem; - string outputResolutionJson; + string? maker = IMetadata.GetMaker(exifDirectory); + string? model = IMetadata.GetModel(exifDirectory); using Bitmap source = new(resizedFileHolder.FullName); - int artist = MetadataExtractor.Formats.Exif.ExifDirectoryBase.TagArtist; - int fileSource = MetadataExtractor.Formats.Exif.ExifDirectoryBase.TagFileSource; - 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; width = location.Right - location.Left; height = location.Bottom - location.Top; - locationJson = JsonSerializer.Serialize(face.Location); faceEncodingJson = JsonSerializer.Serialize(face.FaceEncoding); - outputResolutionJson = JsonSerializer.Serialize(face.OutputResolution); rectangle = new Rectangle(location.Left, location.Top, width, height); + 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)) { using (graphics = Graphics.FromImage(bitmap)) graphics.DrawImage(source, new Rectangle(0, 0, width, height), rectangle, GraphicsUnit.Pixel); - propertyItem = IProperty.GetPropertyItem(_ConstructorInfo, fileSource, type, locationJson); + propertyItem = Property.Models.Stateless.IProperty.GetPropertyItem(_ConstructorInfo, artist, type, faceFileJson); bitmap.SetPropertyItem(propertyItem); - propertyItem = IProperty.GetPropertyItem(_ConstructorInfo, artist, type, outputResolutionJson); + propertyItem = Property.Models.Stateless.IProperty.GetPropertyItem(_ConstructorInfo, userComment, type, faceEncodingJson); bitmap.SetPropertyItem(propertyItem); - propertyItem = 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); @@ -213,7 +229,7 @@ public class D_Face } } - private List GetFaces(string outputResolution, Shared.Models.Property property, MappingFromItem mappingFromItem, Dictionary outputResolutionToResize, List? locations) + private List GetFaces(string outputResolution, string cResultsFullGroupDirectory, Shared.Models.Property property, MappingFromItem mappingFromItem, Dictionary outputResolutionToResize, List locations) { if (_PropertyConfiguration.NumberOfJitters is null) throw new NullReferenceException(nameof(_PropertyConfiguration.NumberOfJitters)); @@ -222,7 +238,24 @@ public class D_Face List results = []; FaceRecognitionDotNet.Image? unknownImage; try - { unknownImage = FaceRecognition.LoadImageFile(mappingFromItem.ResizedFileHolder.FullName); } + { + if (mappingFromItem.ResizedFileHolder.ExtensionLowered != ".tif") + unknownImage = FaceRecognition.LoadImageFile(mappingFromItem.ResizedFileHolder.FullName); + else + { + int outputQuality = 100; + string extension = ".png"; + string file = Path.Combine(cResultsFullGroupDirectory, $"{mappingFromItem.ResizedFileHolder.Name}{extension}"); + (ImageCodecInfo imageCodecInfo, EncoderParameters encoderParameters, string filenameExtension) = C_Resize.GetTuple(extension, outputQuality); +#pragma warning disable CA1416 + System.Drawing.Image image = System.Drawing.Image.FromFile(mappingFromItem.ResizedFileHolder.FullName); + image.Save(Path.Combine(cResultsFullGroupDirectory, $"{mappingFromItem.ResizedFileHolder.Name}{filenameExtension}"), imageCodecInfo, encoderParameters); + image.Dispose(); +#pragma warning restore CA1416 + unknownImage = FaceRecognition.LoadImageFile(file); + File.Delete(file); + } + } catch (Exception) { unknownImage = null; } if (unknownImage is not null) @@ -260,69 +293,13 @@ public class D_Face #pragma warning restore CA1416 - private static List GetLocationContainers(string outputResolution, ReadOnlyCollection locationContainers, Dictionary outputResolutionToResize, List faces) - { - List results = []; - string? json; - Location? location; - Rectangle? rectangle; - List skip = []; - OutputResolution? outputResolutionCheck = null; - (int outputResolutionWidth, int outputResolutionHeight, int outputResolutionOrientation) = Resize.Models.Stateless.Methods.IResize.Get(outputResolution, outputResolutionToResize); - foreach (Shared.Models.Face face in faces) - { - if (face.Location is null || face.OutputResolution is null) - continue; - skip.Add(Shared.Models.Stateless.Methods.ILocation.GetWholePercentages(face.Location, Shared.Models.Stateless.ILocation.Digits, face.OutputResolution)); - } - foreach (LocationContainer locationContainer in locationContainers) - { - if (locationContainer.ExifDirectory is null) - continue; - if (skip.Contains(locationContainer.WholePercentages)) - continue; - foreach (Shared.Models.Face face in faces) - { - if (face.Location is not null && face.OutputResolution is not null) - continue; - json = Metadata.Models.Stateless.Methods.IMetadata.GetOutputResolution(locationContainer.ExifDirectory); - if (json is null) - continue; - outputResolutionCheck = JsonSerializer.Deserialize(json); - if (outputResolutionCheck is null || outputResolutionCheck.Width != outputResolutionWidth || outputResolutionCheck.Height != outputResolutionHeight) - continue; - rectangle = Shared.Models.Stateless.Methods.ILocation.GetRectangle(Shared.Models.Stateless.ILocation.Digits, outputResolutionCheck, locationContainer.WholePercentages); - if (rectangle is null) - continue; - location = Shared.Models.Stateless.Methods.ILocation.GetLocation(outputResolutionHeight, rectangle.Value, outputResolutionWidth); - if (location is null) - continue; - if (!results.Any(l => l.WholePercentages == locationContainer.WholePercentages)) - results.Add(new(locationContainer.CreationDateOnly, - locationContainer.ExifDirectory, - locationContainer.DirectoryNumber, - locationContainer.DisplayDirectoryName, - locationContainer.FilePath, - locationContainer.FromDistanceContent, - locationContainer.Id, - location, - locationContainer.PersonKey, - rectangle.Value, - locationContainer.WholePercentages)); - } - } - if (results.Count > 0) - outputResolutionCheck = null; - return results; - } - - public List GetFaces(string outputResolution, string dResultsFullGroupDirectory, FilePath filePath, List> subFileTuples, List parseExceptions, Shared.Models.Property property, MappingFromItem mappingFromItem, Dictionary outputResolutionToResize, ReadOnlyCollection? locationContainers, List? mappingFromPhotoPrismCollection) + public List GetFaces(string outputResolution, string cResultsFullGroupDirectory, string dResultsFullGroupDirectory, FilePath filePath, List> subFileTuples, List parseExceptions, Shared.Models.Property property, MappingFromItem mappingFromItem, Dictionary outputResolutionToResize, List? mappingFromPhotoPrismCollection) { List? results; if (string.IsNullOrEmpty(dResultsFullGroupDirectory)) throw new NullReferenceException(nameof(dResultsFullGroupDirectory)); string? json; - List? locations; + List locations; 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(); (_, int directoryIndex) = Shared.Models.Stateless.Methods.IPath.GetDirectoryNameAndIndex(_PropertyConfiguration, filePath); @@ -359,18 +336,13 @@ public class D_Face parseExceptions.Add(nameof(D_Face)); } } - List collection; - if (results is null || locationContainers is null) - collection = []; - else - collection = GetLocationContainers(outputResolution, locationContainers, outputResolutionToResize, results); if (!_LoadPhotoPrismLocations || mappingFromPhotoPrismCollection is null || results is null) - locations = (from l in collection where l is not null select l.Location).ToList(); + locations = []; else - locations = Shared.Models.Stateless.Methods.ILocation.GetLocations(collection, results, mappingFromPhotoPrismCollection, _RectangleIntersectMinimum); - if (results is null || (locations is not null && locations.Count > 0)) + locations = Shared.Models.Stateless.Methods.ILocation.GetLocations(results, mappingFromPhotoPrismCollection, _RectangleIntersectMinimum); + if (results is null || locations.Count > 0) { - results = GetFaces(outputResolution, property, mappingFromItem, outputResolutionToResize, locations); + results = GetFaces(outputResolution, cResultsFullGroupDirectory, property, mappingFromItem, outputResolutionToResize, locations); if (results.Count == 0) File.Move(mappingFromItem.ResizedFileHolder.FullName, $"{mappingFromItem.ResizedFileHolder.FullName}.err"); else @@ -394,11 +366,12 @@ public class D_Face return results; } - public List<(Shared.Models.Face, FileInfo?, string, bool)> SaveFaces(string f, string dResultsFullGroupDirectory, FilePath filePath, List> subFileTuples, List parseExceptions, MappingFromItem mappingFromItem, 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(); @@ -415,23 +388,91 @@ public class D_Face } 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); - SaveFaces(mappingFromItem.ResizedFileHolder, results); + SaveFaces(mappingFromItem.ResizedFileHolder, exifDirectory, results); } return results; } +#pragma warning disable CA1416 + + private static (string?, string?) Get(string? json) + { + 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? 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); + Bitmap bitmap = new(fileInfo.FullName); + bitmap.SetPropertyItem(propertyItem); + bitmap.Save(checkFile); + bitmap.Dispose(); + File.SetLastWriteTime(checkFile, fileInfo.LastWriteTime); + File.Delete(fileInfo.FullName); + File.Move(checkFile, fileInfo.FullName); + } + } + +#pragma warning restore CA1416 + } \ No newline at end of file diff --git a/FaceParts/Models/_D2_FaceParts.cs b/FaceParts/Models/_D2_FaceParts.cs index 0fbba9f..6b60154 100644 --- a/FaceParts/Models/_D2_FaceParts.cs +++ b/FaceParts/Models/_D2_FaceParts.cs @@ -57,10 +57,10 @@ public class D2_FaceParts _FileGroups.Add(keyValuePair.Key, keyValuePair.Value); } - public void SetAngleBracketCollection(Configuration configuration, string d2ResultsFullGroupDirectory, string sourceDirectory) + public void SetAngleBracketCollection(IPropertyConfiguration propertyConfiguration, string d2ResultsFullGroupDirectory, string sourceDirectory) { _AngleBracketCollection.Clear(); - _AngleBracketCollection.AddRange(IResult.GetDirectoryInfoCollection(configuration, + _AngleBracketCollection.AddRange(IResult.GetDirectoryInfoCollection(propertyConfiguration, sourceDirectory, d2ResultsFullGroupDirectory, contentDescription: "n gif file(s) for each face found", @@ -69,7 +69,7 @@ public class D2_FaceParts converted: true)); } - public string GetFacePartsDirectory(Configuration configuration, string dResultsFullGroupDirectory, Item item, bool includeNameWithoutExtension) + public string GetFacePartsDirectory(IPropertyConfiguration propertyConfiguration, string dResultsFullGroupDirectory, Item item, bool includeNameWithoutExtension) { string result; bool angleBracketCollectionAny = _AngleBracketCollection.Count != 0; @@ -77,7 +77,7 @@ public class D2_FaceParts { if (item.FilePath.DirectoryName is null) throw new NullReferenceException(nameof(item.FilePath.DirectoryName)); - SetAngleBracketCollection(configuration, dResultsFullGroupDirectory, item.FilePath.DirectoryName); + SetAngleBracketCollection(propertyConfiguration, dResultsFullGroupDirectory, item.FilePath.DirectoryName); } if (includeNameWithoutExtension) result = Path.Combine(_AngleBracketCollection[0].Replace("<>", _PropertyConfiguration.ResultContent), item.FilePath.NameWithoutExtension); @@ -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 44c5c2c..b340391 100644 --- a/FaceRecognitionDotNet/FaceRecognition.cs +++ b/FaceRecognitionDotNet/FaceRecognition.cs @@ -167,7 +167,13 @@ public class FaceRecognition : DisposableObject } else { - ShapePredictor posePredictor = _PredictorModel switch { PredictorModel.Large => _PosePredictor68Point, PredictorModel.Small => _PosePredictor5Point, _ => throw new Exception() }; + ShapePredictor posePredictor = _PredictorModel switch + { + PredictorModel.Large => _PosePredictor68Point, + PredictorModel.Small => _PosePredictor5Point, + PredictorModel.Custom => throw new NotImplementedException(), + _ => throw new Exception() + }; foreach (Location location in locations) { DlibDotNet.Rectangle rectangle = new(location.Left, location.Top, location.Right, location.Bottom); @@ -197,7 +203,7 @@ public class FaceRecognition : DisposableObject return results; } - public List<(Location, FaceEncoding?, Dictionary?)> GetCollection(Image image, List? locations, bool includeFaceEncoding, bool includeFaceParts) + public List<(Location, FaceEncoding?, Dictionary?)> GetCollection(Image image, List locations, bool includeFaceEncoding, bool includeFaceParts) { List<(Location, FaceEncoding?, Dictionary?)> results = []; if (image is null) @@ -206,9 +212,7 @@ public class FaceRecognition : DisposableObject ThrowIfDisposed(); if (_PredictorModel == PredictorModel.Custom) throw new NotSupportedException("FaceRecognition.PredictorModel.Custom is not supported."); - if (locations is null) - locations = GetLocations(image); - else if (locations.Count == 0) + if (locations.Count == 0) locations.AddRange(GetLocations(image)); List fullObjectDetections = GetFullObjectDetections(image, locations); if (fullObjectDetections.Count != locations.Count) @@ -408,22 +412,50 @@ 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) { +#pragma warning disable CA1513 if (faceDistance.Encoding is not FaceEncoding faceEncoding || faceEncoding.IsDisposed) throw new ObjectDisposedException($"{nameof(faceDistances)} contains disposed object."); +#pragma warning restore CA1513 using (Matrix diff = faceEncoding.Encoding - faceEncodingToCompare.Encoding) length = DlibDotNet.Dlib.Length(diff); result = new(faceDistance, length); diff --git a/Instance/DlibDotNet.cs b/Instance/DlibDotNet.cs index 84027e9..09a241b 100644 --- a/Instance/DlibDotNet.cs +++ b/Instance/DlibDotNet.cs @@ -22,23 +22,25 @@ using WindowsShortcutFactory; namespace View_by_Distance.Instance; -public partial class DlibDotNet +public partial class DlibDotNet : IDlibDotNet, IDisposable { [GeneratedRegex(@"[\\,\/,\:,\*,\?,\"",\<,\>,\|]")] private static partial Regex CameraRegex(); private readonly D_Face _Faces; + private ProgressBar? _ProgressBar; private readonly C_Resize _Resize; private readonly F_Random _Random; private readonly IConsole _Console; private readonly E_Distance _Distance; - private readonly IBlurHasher _BlurHasher; private readonly D2_FaceParts _FaceParts; + private readonly IBlurHasher _BlurHasher; private readonly AppSettings _AppSettings; private readonly List _Exceptions; private readonly ILogger? _Logger; private readonly IsEnvironment _IsEnvironment; + private readonly DistanceLimits _DistanceLimits; private readonly bool _PropertyRootExistedBefore; private readonly Models.Configuration _Configuration; private readonly bool _ArgZeroIsConfigurationRootDirectory; @@ -111,7 +113,8 @@ public partial class DlibDotNet (ImageCodecInfo imageCodecInfo, EncoderParameters encoderParameters, string filenameExtension) = C_Resize.GetGifLowQuality(); _FaceParts = new D2_FaceParts(_Configuration.PropertyConfiguration, imageCodecInfo, encoderParameters, filenameExtension, configuration.CheckDFaceAndUpWriteDates, configuration.OverrideForFaceLandmarkImages); } - _MapConfiguration = Get(configuration, _Faces.FileNameExtension, _Faces.HiddenFileNameExtension, _FaceParts.FileNameExtension); + _DistanceLimits = new(_Configuration.FaceAreaPermyriad, _Configuration.FaceConfidencePercent, _Configuration.FaceDistancePermyriad, _Configuration.RangeDaysDeltaTolerance, _Configuration.RangeDistanceTolerance, _Configuration.RangeFaceAreaPermyriadTolerance, _Configuration.RangeFaceConfidence, _Configuration.SortingMaximumPerFaceShouldBeHigh); + _MapConfiguration = Get(configuration, _DistanceLimits, _Faces.FileNameExtension, _Faces.HiddenFileNameExtension, _FaceParts.FileNameExtension); _Distance = new(configuration.DistanceMoveUnableToMatch, configuration.DistanceRenameToMatch, configuration.FaceConfidencePercent, configuration.RangeDistanceTolerance, configuration.RectangleIntersectMinimums); if (_PropertyRootExistedBefore || !_ArgZeroIsConfigurationRootDirectory) personContainers = new(new List()); @@ -165,6 +168,15 @@ public partial class DlibDotNet _Logger?.LogInformation("First run completed. Run again if wanted"); } + void IDlibDotNet.Tick() => + _ProgressBar?.Tick(); + + void IDisposable.Dispose() + { + _ProgressBar?.Dispose(); + GC.SuppressFinalize(this); + } + private static void Verify(Models.Configuration configuration) { if (configuration.RangeDaysDeltaTolerance.Length != 3) @@ -232,35 +244,84 @@ public partial class DlibDotNet throw new Exception("Configuration has to match interface!"); if (configuration.LocationFactor != Shared.Models.Stateless.ILocation.Factor) throw new Exception("Configuration has to match interface!"); + if (configuration.SaveSortingWithoutPerson && configuration.JLinks.Length > 0) + throw new Exception("Configuration has SaveSortingWithoutPerson and JLinks!"); + if (configuration.SaveSortingWithoutPerson && !string.IsNullOrEmpty(configuration.FocusModel)) + throw new Exception("Configuration has SaveSortingWithoutPerson and FocusModel!"); + if (configuration.SaveSortingWithoutPerson && !string.IsNullOrEmpty(configuration.FocusDirectory)) + throw new Exception("Configuration has SaveSortingWithoutPerson and FocusDirectory!"); } - private ReadOnlyCollection GetNotNineCollection(ReadOnlyCollection filesCollection) + private ReadOnlyCollection GetNotNineCollection(ReadOnlyCollection> filePathsCollection) { List results = []; - FilePath filePath; + FileInfo fileInfo; FileHolder fileHolder; - foreach (string[] files in filesCollection) + FilePath checkFilePath; + foreach (ReadOnlyCollection filePaths in filePathsCollection) { - foreach (string file in files) + foreach (FilePath filePath in filePaths) { - if (!file.Contains(" !9")) + if (!filePath.FullName.Contains(" !9")) continue; - fileHolder = Shared.Models.Stateless.Methods.IFileHolder.Get(file); - filePath = FilePath.Get(_Configuration.PropertyConfiguration, fileHolder, index: null); - if (filePath.Id is null) + fileInfo = new(filePath.FullName); + fileHolder = Shared.Models.Stateless.Methods.IFileHolder.Get(fileInfo); + if (!fileInfo.Attributes.HasFlag(FileAttributes.Hidden)) + File.SetAttributes(fileHolder.FullName, FileAttributes.Hidden); + checkFilePath = FilePath.Get(_Configuration.PropertyConfiguration, fileHolder, index: null); + if (checkFilePath.Id is null) continue; - results.Add(filePath.Id.Value); + results.Add(checkFilePath.Id.Value); } } return new(results); } + private static void DeleteContinueFiles(ReadOnlyCollection personContainers) + { + foreach (PersonContainer personContainer in personContainers) + { + foreach (FilePath filePath in personContainer.DisplayDirectoryAllFilePaths) + { + if (filePath.ExtensionLowered != ".continue") + continue; + File.Delete(filePath.FullName); + } + } + } + + 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; string message; MapLogic? mapLogic; - Container[] containers; A_Property propertyLogic; string eDistanceContentDirectory; string? a2PeopleContentDirectory; @@ -274,10 +335,11 @@ public partial class DlibDotNet const string directorySearchFilter = "*"; string? filesCollectionRootDirectory = null; bool configurationOutputResolutionsHas = false; + ReadOnlyCollection readOnlyContainers; ReadOnlyCollection? notNineCollection = null; ReadOnlyDictionary> personKeyToIds; - ReadOnlyCollection? filesCollection = null; - bool runToDoCollectionFirst = GetRunToDoCollectionFirst(ticks); + ReadOnlyCollection>? filePathsCollection = null; + bool runToDoCollectionFirst = GetRunToDoCollectionFirst(_Configuration, ticks); (aResultsFullGroupDirectory, bResultsFullGroupDirectory) = GetResultsFullGroupDirectories(); ProgressBarOptions options = new() { ProgressCharacter = '─', ProgressBarOnBottom = true, DisableBottomPercentage = true }; Shared.Models.Stateless.Methods.IPath.ChangeDateForEmptyDirectories(_Configuration.PropertyConfiguration.RootDirectory, ticks); @@ -296,8 +358,8 @@ public partial class DlibDotNet configurationOutputResolutionsHas = true; if (!runToDoCollectionFirst) break; - (filesCollectionRootDirectory, filesCollection, filesCollectionCountIsOne) = GetFilesCollectionThenCopyOrMove(ticks, fileSearchFilter, directorySearchFilter, options, outputResolution); - notNineCollection = GetNotNineCollection(filesCollection); + (filesCollectionRootDirectory, filePathsCollection, filesCollectionCountIsOne) = GetFilesCollectionThenCopyOrMove(ticks, fileSearchFilter, directorySearchFilter, options, outputResolution); + notNineCollection = GetNotNineCollection(filePathsCollection); break; } fPhotoPrismContentDirectory = Property.Models.Stateless.IResult.GetResultsDateGroupDirectory(_Configuration.PropertyConfiguration, nameof(F_PhotoPrism), _Configuration.PropertyConfiguration.ResultContent); @@ -305,8 +367,8 @@ public partial class DlibDotNet propertyLogic = new(_AppSettings.MaxDegreeOfParallelism, _Configuration.PropertyConfiguration, _Resize.FileNameExtension, _Configuration.Reverse, aResultsFullGroupDirectory); if (filesCollectionCountIsOne) { - if (filesCollection is null) - throw new NullReferenceException(nameof(filesCollection)); + if (filePathsCollection is null) + throw new NullReferenceException(nameof(filePathsCollection)); string resultsGroupDirectory; a2PeopleContentDirectory = null; eDistanceContentDirectory = Path.Combine($"{Path.GetPathRoot(argZero)}", _Configuration.PropertyConfiguration.ResultContent); @@ -317,7 +379,7 @@ public partial class DlibDotNet resultsGroupDirectory = Property.Models.Stateless.IResult.GetResultsGroupDirectory(_Configuration.PropertyConfiguration, string.Empty, create: true); _ = Shared.Models.Stateless.Methods.IPath.DeleteEmptyDirectories(resultsGroupDirectory); } - argZero = SaveUrlAndGetNewRootDirectory(filesCollection.First()); + argZero = SaveUrlAndGetNewRootDirectory(filePathsCollection.First()); _Configuration.PropertyConfiguration.ChangeRootDirectory(argZero); (aResultsFullGroupDirectory, bResultsFullGroupDirectory) = GetResultsFullGroupDirectories(); propertyRoot = Property.Models.Stateless.IResult.GetResultsGroupDirectory(_Configuration.PropertyConfiguration, nameof(A_Property), create: false); @@ -325,53 +387,53 @@ public partial class DlibDotNet } if (configurationOutputResolutionsHas) { - int count; foreach (string outputResolution in _Configuration.OutputResolutions) { if (outputResolution.Any(char.IsNumber)) continue; (cResultsFullGroupDirectory, _, _, _) = GetResultsFullGroupDirectories(outputResolution); filesCollectionRootDirectory = Path.Combine(cResultsFullGroupDirectory, _Configuration.PropertyConfiguration.ResultContent); - filesCollection = IDirectory.GetFilesCollection(filesCollectionRootDirectory, directorySearchFilter, fileSearchFilter, useCeilingAverage: true); - count = filesCollection.Select(l => l.Length).Sum(); + filePathsCollection = IDirectory.GetFilePathCollections(_Configuration.PropertyConfiguration, filesCollectionRootDirectory, directorySearchFilter, fileSearchFilter, useCeilingAverage: true); break; } } - if (filesCollectionRootDirectory is null || filesCollection is null) - throw new NullReferenceException(nameof(filesCollection)); + if (filesCollectionRootDirectory is null || filePathsCollection is null) + throw new NullReferenceException(nameof(filePathsCollection)); + string aPropertySingletonDirectory = Path.Combine(aResultsFullGroupDirectory, _Configuration.PropertyConfiguration.ResultSingleton); + if (!Directory.Exists(aPropertySingletonDirectory)) + _ = Directory.CreateDirectory(aPropertySingletonDirectory); + int count = filePathsCollection.Select(l => l.Count).Sum(); + SaveDistinctIds(_Configuration.PropertyConfiguration, bResultsFullGroupDirectory, filePathsCollection); message = $") Building Container(s) - {(int)Math.Floor(new TimeSpan(DateTime.Now.Ticks - ticks).TotalSeconds)} total second(s)"; - using (ProgressBar progressBar = new(2, message, options)) - { - progressBar.Tick(); - string aPropertySingletonDirectory = Path.Combine(aResultsFullGroupDirectory, _Configuration.PropertyConfiguration.ResultSingleton); - if (!Directory.Exists(aPropertySingletonDirectory)) - _ = Directory.CreateDirectory(aPropertySingletonDirectory); - (t, containers) = Shared.Models.Stateless.Methods.IContainer.GetContainers(_Configuration.PropertyConfiguration, aPropertySingletonDirectory, filesCollectionRootDirectory, filesCollection); - progressBar.Tick(); - } - ReadOnlyCollection readOnlyContainers = new(containers); - SaveDistinctIds(_Configuration.PropertyConfiguration, bResultsFullGroupDirectory, readOnlyContainers); + _ProgressBar = new(count, message, options); + readOnlyContainers = Shared.Models.Stateless.Methods.IContainer.GetContainers(this, _Configuration.PropertyConfiguration, aPropertySingletonDirectory, filesCollectionRootDirectory, filePathsCollection); + _ProgressBar.Dispose(); mapLogic ??= new(_AppSettings.MaxDegreeOfParallelism, _Configuration.PropertyConfiguration, _MapConfiguration, _Distance, personContainers, ticks, a2PeopleContentDirectory, a2PeopleSingletonDirectory, eDistanceContentDirectory); - FullDoWork(argZero, propertyRoot, ticks, aResultsFullGroupDirectory, bResultsFullGroupDirectory, fPhotoPrismSingletonDirectory, t, readOnlyContainers, propertyLogic, mapLogic); - ReadOnlyCollection distinctFilteredItems = Shared.Models.Stateless.Methods.IContainer.GetItems(_Configuration.PropertyConfiguration, readOnlyContainers, distinctItems: true, filterItems: true); + DeleteContinueFiles(personContainers); + if (!runToDoCollectionFirst) + MapFaceFileLogic(ticks, personContainers, mapLogic, a2PeopleContentDirectory, eDistanceContentDirectory, options); + FullDoWork(argZero, propertyRoot, ticks, aResultsFullGroupDirectory, bResultsFullGroupDirectory, fPhotoPrismSingletonDirectory, count, readOnlyContainers, propertyLogic, mapLogic); + ReadOnlyCollection distinctValidImageItems = Shared.Models.Stateless.Methods.IContainer.GetValidImageItems(_Configuration.PropertyConfiguration, readOnlyContainers, distinctItems: true, filterItems: true); if (_Configuration.LookForAbandoned) { string dResultsFullGroupDirectory; string d2ResultsFullGroupDirectory; foreach (string outputResolution in _Configuration.OutputResolutions) { + _ProgressBar = new(5, nameof(mapLogic.LookForAbandoned), options); (cResultsFullGroupDirectory, _, dResultsFullGroupDirectory, d2ResultsFullGroupDirectory) = GetResultsFullGroupDirectories(outputResolution); - mapLogic.LookForAbandoned(_Configuration.PropertyConfiguration, bResultsFullGroupDirectory, readOnlyContainers, cResultsFullGroupDirectory, dResultsFullGroupDirectory, d2ResultsFullGroupDirectory); + mapLogic.LookForAbandoned(this, _Configuration.PropertyConfiguration, bResultsFullGroupDirectory, readOnlyContainers, cResultsFullGroupDirectory, dResultsFullGroupDirectory, d2ResultsFullGroupDirectory); + _ProgressBar.Dispose(); } } _Distance.Clear(); - ReadOnlyCollection distinctFilteredFaces = Map.Models.Stateless.Methods.IMapLogic.GetFaces(distinctFilteredItems); - ReadOnlyCollection distinctFilteredMappingCollection = GetMappings(_Configuration.PropertyConfiguration, eDistanceContentDirectory, readOnlyContainers, mapLogic, distinctItems: true); + ReadOnlyCollection distinctValidImageFaces = Map.Models.Stateless.Methods.IMapLogic.GetFaces(distinctValidImageItems); + ReadOnlyCollection distinctValidImageMappingCollection = GetMappings(_Configuration.PropertyConfiguration, eDistanceContentDirectory, readOnlyContainers, mapLogic, distinctItems: true); if (runToDoCollectionFirst) { if (!Directory.Exists(eDistanceContentDirectory)) _ = Directory.CreateDirectory(eDistanceContentDirectory); - string json = JsonSerializer.Serialize(distinctFilteredMappingCollection); + string json = JsonSerializer.Serialize(distinctValidImageMappingCollection); File.WriteAllText(Path.Combine(eDistanceContentDirectory, $"{ticks}.json"), json); } foreach (string outputResolution in _Configuration.OutputResolutions) @@ -380,19 +442,19 @@ public partial class DlibDotNet break; personKeyToIds = mapLogic.GetPersonKeyToIds(); if (_Configuration.SavePropertyShortcutsForOutputResolutions.Contains(outputResolution)) - SavePropertyShortcutsForOutputResolutions(eDistanceContentDirectory, distinctFilteredItems); + SavePropertyShortcutsForOutputResolutions(eDistanceContentDirectory, distinctValidImageItems); if (!string.IsNullOrEmpty(a2PeopleContentDirectory) && _Configuration.SaveShortcutsForOutputResolutions.Contains(outputResolution)) - mapLogic.SaveShortcutsForOutputResolutionsPreMapLogic(eDistanceContentDirectory, personKeyToIds, distinctFilteredMappingCollection); + mapLogic.SaveShortcutsForOutputResolutionsPreMapLogic(eDistanceContentDirectory, personKeyToIds, distinctValidImageMappingCollection); if (!string.IsNullOrEmpty(a2PeopleContentDirectory) && _Configuration.JLinks.Where(l => !string.IsNullOrEmpty(l)).Any() && _Configuration.SaveFilteredOriginalImagesFromJLinksForOutputResolutions.Contains(outputResolution)) - mapLogic.SaveFilteredOriginalImagesFromJLinks(_Configuration.JLinks, personContainers, a2PeopleContentDirectory, personKeyToIds, distinctFilteredMappingCollection); + mapLogic.SaveFilteredOriginalImagesFromJLinks(_Configuration.JLinks, personContainers, a2PeopleContentDirectory, personKeyToIds, distinctValidImageMappingCollection); if (_ArgZeroIsConfigurationRootDirectory && _Configuration.SaveResizedSubfiles && outputResolution == _Configuration.OutputResolutions[0] && _Configuration.LoadOrCreateThenSaveDistanceResultsForOutputResolutions.Contains(outputResolution) && _Exceptions.Count == 0) - MapLogic(ticks, readOnlyContainers, fPhotoPrismContentDirectory, mapLogic, outputResolution, new(personKeyToIds), distinctFilteredFaces, distinctFilteredMappingCollection); - if (runToDoCollectionFirst && _Configuration.SaveRandomForOutputResolutions.Contains(outputResolution) && personKeyToIds.Count > 0 && distinctFilteredMappingCollection.Count > 0) - _Random.Random(_Configuration.PropertyConfiguration, _Configuration.RadomUseBirthdayMinimum, _Configuration.ValidKeyWordsToIgnoreInRandom, personKeyToIds, notNineCollection, distinctFilteredMappingCollection); + MapLogic(ticks, readOnlyContainers, fPhotoPrismContentDirectory, mapLogic, outputResolution, new(personKeyToIds), distinctValidImageFaces, distinctValidImageMappingCollection); + if (runToDoCollectionFirst && _Configuration.SaveRandomForOutputResolutions.Contains(outputResolution) && personKeyToIds.Count > 0 && distinctValidImageMappingCollection.Count > 0) + _Random.Random(_Configuration.PropertyConfiguration, _Configuration.ImmichAssetsFile, _Configuration.RadomUseBirthdayMinimum, _Configuration.ValidKeyWordsToIgnoreInRandom, personKeyToIds, notNineCollection, distinctValidImageMappingCollection); if (_IsEnvironment.Development) continue; if (!_IsEnvironment.Development) @@ -410,47 +472,52 @@ public partial class DlibDotNet } } - private bool GetRunToDoCollectionFirst(long ticks) + private bool GetRunToDoCollectionFirst(Models.Configuration configuration, long ticks) { - bool result = false; - string[] directories; - directories = Directory.GetDirectories(_Configuration.PropertyConfiguration.RootDirectory, "*", SearchOption.TopDirectoryOnly); - if (directories.Length == 0) - result = true; - else + bool result = configuration.SaveSortingWithoutPerson; + if (!result) + result = !IId.IsOffsetDeterministicHashCode(configuration.PropertyConfiguration); + if (!result) { - string seasonDirectory; - DirectoryInfo directoryInfo; - DateTime dateTime = new(ticks); - string rootDirectory = _Configuration.PropertyConfiguration.RootDirectory; - string eDistanceContentDirectory = Property.Models.Stateless.IResult.GetResultsDateGroupDirectory(_Configuration.PropertyConfiguration, nameof(E_Distance), _Configuration.PropertyConfiguration.ResultContent); - (int season, string seasonName) = Shared.Models.Stateless.Methods.IProperty.GetSeason(dateTime.DayOfYear); - FileSystemInfo fileSystemInfo = new DirectoryInfo(eDistanceContentDirectory); - string[] checkDirectories = - [ - Path.Combine(rootDirectory, "Ancestry"), - Path.Combine(rootDirectory, "Facebook"), - Path.Combine(rootDirectory, "LinkedIn"), - rootDirectory, - ]; - foreach (string checkDirectory in checkDirectories) + string[] directories; + directories = Directory.GetDirectories(_Configuration.PropertyConfiguration.RootDirectory, "*", SearchOption.TopDirectoryOnly); + if (directories.Length == 0) + result = true; + else { - if (checkDirectory == rootDirectory) - seasonDirectory = Path.Combine(checkDirectory, $"{dateTime.Year}.{season} {seasonName}"); - else - seasonDirectory = Path.Combine(checkDirectory, $"{dateTime.Year}.{season} {seasonName} {Path.GetFileName(checkDirectory)}"); - if (!Directory.Exists(seasonDirectory)) - _ = Directory.CreateDirectory(seasonDirectory); - if (result) - continue; - directories = Directory.GetDirectories(checkDirectory, "*", SearchOption.TopDirectoryOnly); - foreach (string directory in directories) + string seasonDirectory; + DirectoryInfo directoryInfo; + DateTime dateTime = new(ticks); + string rootDirectory = _Configuration.PropertyConfiguration.RootDirectory; + string eDistanceContentDirectory = Property.Models.Stateless.IResult.GetResultsDateGroupDirectory(_Configuration.PropertyConfiguration, nameof(E_Distance), _Configuration.PropertyConfiguration.ResultContent); + (int season, string seasonName) = Shared.Models.Stateless.Methods.IProperty.GetSeason(dateTime.DayOfYear); + FileSystemInfo fileSystemInfo = new DirectoryInfo(eDistanceContentDirectory); + string[] checkDirectories = + [ + Path.Combine(rootDirectory, "Ancestry"), + Path.Combine(rootDirectory, "Facebook"), + Path.Combine(rootDirectory, "LinkedIn"), + rootDirectory, + ]; + foreach (string checkDirectory in checkDirectories) { - directoryInfo = new(directory); - if (directoryInfo.LastWriteTime > fileSystemInfo.LastWriteTime) + if (checkDirectory == rootDirectory) + seasonDirectory = Path.Combine(checkDirectory, $"{dateTime.Year}.{season} {seasonName}"); + else + seasonDirectory = Path.Combine(checkDirectory, $"{dateTime.Year}.{season} {seasonName} {Path.GetFileName(checkDirectory)}"); + if (!Directory.Exists(seasonDirectory)) + _ = Directory.CreateDirectory(seasonDirectory); + if (result) + continue; + directories = Directory.GetDirectories(checkDirectory, "*", SearchOption.TopDirectoryOnly); + foreach (string directory in directories) { - result = true; - break; + directoryInfo = new(directory); + if (directoryInfo.LastWriteTime > fileSystemInfo.LastWriteTime) + { + result = true; + break; + } } } } @@ -462,12 +529,12 @@ public partial class DlibDotNet return result; } - private string SaveUrlAndGetNewRootDirectory(string[] files) + private string SaveUrlAndGetNewRootDirectory(ReadOnlyCollection filePaths) { string result; - if (files.Length == 0) + if (filePaths.Count == 0) throw new NotSupportedException(); - string? sourceDirectory = Path.GetDirectoryName(files.First()); + string? sourceDirectory = filePaths[0].DirectoryName; if (string.IsNullOrEmpty(sourceDirectory)) throw new NotSupportedException(); Uri uri; @@ -508,7 +575,7 @@ public partial class DlibDotNet return result; } - private void FullDoWork(string argZero, string propertyRoot, long ticks, string aResultsFullGroupDirectory, string bResultsFullGroupDirectory, string fPhotoPrismSingletonDirectory, int t, ReadOnlyCollection readOnlyContainers, A_Property propertyLogic, MapLogic mapLogic) + private void FullDoWork(string argZero, string propertyRoot, long ticks, string aResultsFullGroupDirectory, string bResultsFullGroupDirectory, string fPhotoPrismSingletonDirectory, int count, ReadOnlyCollection readOnlyContainers, A_Property propertyLogic, MapLogic mapLogic) { int total; int notMapped; @@ -516,7 +583,6 @@ public partial class DlibDotNet bool exceptions; int totalSeconds; Container container; - Item[] filteredItems; int totalNotMapped = 0; bool outputResolutionHasNumber; bool anyNullOrNoIsUniqueFileName; @@ -524,6 +590,7 @@ public partial class DlibDotNet string dResultsFullGroupDirectory; string c2ResultsFullGroupDirectory; string d2ResultsFullGroupDirectory; + ReadOnlyCollection filteredItems; int containersLength = readOnlyContainers.Count; List> sourceDirectoryChanges = []; int maxDegreeOfParallelism = _AppSettings.MaxDegreeOfParallelism; @@ -546,13 +613,13 @@ public partial class DlibDotNet continue; if (!_ArgZeroIsConfigurationRootDirectory && !container.SourceDirectory.StartsWith(argZero)) continue; - filteredItems = Shared.Models.Stateless.Methods.IContainer.GetFilterItems(_Configuration.PropertyConfiguration, container); - if (filteredItems.Length == 0) + filteredItems = Shared.Models.Stateless.Methods.IContainer.GetValidImageItems(_Configuration.PropertyConfiguration, container); + if (filteredItems.Count == 0) continue; sourceDirectoryChanges.Clear(); anyNullOrNoIsUniqueFileName = filteredItems.Any(l => !l.IsUniqueFileName); totalSeconds = (int)Math.Floor(new TimeSpan(DateTime.Now.Ticks - ticks).TotalSeconds); - message = $"{i + 1:000} [{filteredItems.Length:000}] / {containersLength:000} - {total} / {t} total - {totalSeconds} total second(s) - {outputResolution} - <{container.SourceDirectory}> - total not mapped {totalNotMapped:000000}"; + message = $"{i + 1:000} [{filteredItems.Count:000}] / {containersLength:000} - {total} / {count} total - {totalSeconds} total second(s) - {outputResolution} - <{container.SourceDirectory}> - total not mapped {totalNotMapped:000000}"; propertyLogic.SetAngleBracketCollection(aResultsFullGroupDirectory, container.SourceDirectory, anyNullOrNoIsUniqueFileName); if (outputResolutionHasNumber) _Resize.SetAngleBracketCollection(cResultsFullGroupDirectory, container.SourceDirectory); @@ -589,14 +656,14 @@ public partial class DlibDotNet total += container.Items.Count; } totalSeconds = (int)Math.Floor(new TimeSpan(DateTime.Now.Ticks - ticks).TotalSeconds); - message = $"### [###] / {containersLength:000} - {total} / {t} total - {totalSeconds} total second(s) - {outputResolution} - <> - total not mapped {totalNotMapped:000000}"; + message = $"### [###] / {containersLength:000} - {total} / {count} total - {totalSeconds} total second(s) - {outputResolution} - <> - total not mapped {totalNotMapped:000000}"; ProgressBarOptions options = new() { ProgressCharacter = '─', ProgressBarOnBottom = true, DisableBottomPercentage = true }; using ProgressBar progressBar = new(1, message, options); progressBar.Tick(); } } - private static void SaveDistinctIds(Property.Models.Configuration propertyConfiguration, string bResultsFullGroupDirectory, ReadOnlyCollection readOnlyContainers) + private static void SaveDistinctIds(Property.Models.Configuration propertyConfiguration, string bResultsFullGroupDirectory, ReadOnlyCollection> filePathsCollection) { string paddedId; List distinct = []; @@ -604,21 +671,19 @@ public partial class DlibDotNet string bMetadataCollectionDirectory = Path.Combine(bResultsFullGroupDirectory, propertyConfiguration.ResultCollection); if (!Directory.Exists(bMetadataCollectionDirectory)) _ = Directory.CreateDirectory(bMetadataCollectionDirectory); - foreach (Container container in readOnlyContainers) + foreach (ReadOnlyCollection filePaths in filePathsCollection) { - if (container.Items.Count == 0) + if (filePaths.Count == 0) continue; - foreach (Item item in container.Items) + foreach (FilePath filePath in filePaths) { - if (item.Property?.Id is null) + if (filePath.Id is null) continue; - if (item.Property.Id != item.FilePath.Id) - throw new NotSupportedException(); - if (distinct.Contains(item.Property.Id.Value)) + if (distinct.Contains(filePath.Id.Value)) continue; - distinct.Add(item.Property.Id.Value); - paddedId = IId.GetPaddedId(propertyConfiguration, item.Property.Id.Value, item.FilePath.IsIgnore, index: null); - identifiers.Add(new(item.Property.Id.Value, paddedId)); + distinct.Add(filePath.Id.Value); + paddedId = IId.GetPaddedId(propertyConfiguration, filePath.Id.Value, filePath.HasIgnoreKeyword, filePath.HasDateTimeOriginal, index: null); + identifiers.Add(new(filePath.Id.Value, paddedId)); } } string json = JsonSerializer.Serialize(identifiers.OrderBy(l => l.PaddedId).ToArray(), IdentifierCollectionSourceGenerationContext.Default.IdentifierArray); @@ -627,7 +692,7 @@ public partial class DlibDotNet private ReadOnlyCollection GetMappings(Property.Models.Configuration propertyConfiguration, string eDistanceContentDirectory, ReadOnlyCollection readOnlyContainers, MapLogic mapLogic, bool distinctItems) { - ReadOnlyCollection results; + List results = []; int count = 0; int notMapped; Mapping mapping; @@ -636,15 +701,14 @@ public partial class DlibDotNet string focusRelativePath; bool? isFocusRelativePath; DateTime[] containerDateTimes; - IEnumerable filteredItems; MappingFromItem mappingFromItem; - List mappingCollection = []; + ReadOnlyCollection filteredItems; foreach (Container container in readOnlyContainers) { if (container.Items.Count == 0) continue; - filteredItems = Shared.Models.Stateless.Methods.IContainer.GetFilterItems(propertyConfiguration, container); - if (!filteredItems.Any()) + filteredItems = Shared.Models.Stateless.Methods.IContainer.GetValidImageItems(propertyConfiguration, container); + if (filteredItems.Count == 0) continue; containerDateTimes = Shared.Models.Stateless.Methods.IContainer.GetContainerDateTimes(filteredItems); focusRelativePath = Path.GetFullPath(string.Concat(_Configuration.PropertyConfiguration.RootDirectory, _Configuration.FocusDirectory)); @@ -665,24 +729,25 @@ public partial class DlibDotNet foreach (Shared.Models.Face face in item.Faces) { if (face.Mapping is null) + throw new NotSupportedException(); + if (face.FaceEncoding is null || face.Location is null || face.OutputResolution is null) continue; anyValidFaces = true; - mappingCollection.Add(face.Mapping); + results.Add(face.Mapping); } if (!anyValidFaces) { (mapping, notMapped) = GetMappingAndUpdateMappingFromPerson(mapLogic, item, isFocusRelativePath, mappingFromItem); - mappingCollection.Add(mapping); + results.Add(mapping); } } } if (_Configuration.MoveToDecade && _Configuration.LocationContainerDistanceTolerance is null) _ = Shared.Models.Stateless.Methods.IPath.DeleteEmptyDirectories(eDistanceContentDirectory); - results = new((from l in mappingCollection orderby l.MappingFromItem.Id select l).ToArray()); - return results; + return new(results); } - private static void SavePropertyShortcutsForOutputResolutions(string eDistanceContentDirectory, ReadOnlyCollection distinctFilteredItems) + private static void SavePropertyShortcutsForOutputResolutions(string eDistanceContentDirectory, ReadOnlyCollection distinctValidImageItems) { #if VerifyItem bool found; @@ -713,7 +778,7 @@ public partial class DlibDotNet List distinct = []; WindowsShortcut windowsShortcut; List<(string, string, string)> collection = []; - foreach (Item item in distinctFilteredItems) + foreach (Item item in distinctValidImageItems) { if (item.Property?.Id is null) continue; @@ -726,7 +791,7 @@ public partial class DlibDotNet continue; distinct.Add(directory); } - foreach (Item item in distinctFilteredItems) + foreach (Item item in distinctValidImageItems) { if (item.Property?.Id is null || item.Property.DateTimeOriginal is null) continue; @@ -771,27 +836,119 @@ public partial class DlibDotNet } } - private void MapLogic(long ticks, ReadOnlyCollection containers, string fPhotoPrismContentDirectory, MapLogic mapLogic, string outputResolution, ReadOnlyDictionary> personKeyToIds, ReadOnlyCollection distinctFilteredFaces, ReadOnlyCollection distinctFilteredMappingCollection) + 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); + } + + public ReadOnlyDictionary GetOnlyOne(IDistanceLimits distanceLimits, ReadOnlyCollection matrix) + { + Dictionary results = []; + List added = []; + LocationContainer? tryGetValue; + foreach (LocationContainer locationContainer in matrix) + { + if (_Configuration.SaveIndividually) + break; + if (locationContainer.LengthSource is null) + continue; + if (_Configuration.UseExtraPersonKeyCheck) + { + if (results.TryGetValue(locationContainer.LengthSource.Name, out tryGetValue)) + { + if (locationContainer.PersonKey is not null && tryGetValue.PersonKey is not null && locationContainer.PersonKey.Value != tryGetValue.PersonKey) + _ = results.Remove(locationContainer.LengthSource.Name); + continue; + } + } + if (added.Contains(locationContainer.LengthSource.Name)) + continue; + added.Add(locationContainer.LengthSource.Name); + results.Add(locationContainer.LengthSource.Name, locationContainer); + } + 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, _MapConfiguration, _Faces, ticks, 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) + results = []; + else + { + 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) + results = []; + else + { + 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(); + ReadOnlyDictionary onlyOne = GetOnlyOne(distanceLimits, matrix); + if (onlyOne.Count == 0) + results = []; + else + { + ReadOnlyDictionary personKeyToPersonContainer = mapLogic.GetPersonKeyToPersonContainer(); + results = mapLogic.GetSaveContainers(cResultsFullGroupDirectory, dResultsFullGroupDirectory, d2ResultsFullGroupDirectory, distanceLimits, onlyOne, personKeyToPersonContainer); + } + } + } + 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); string dFacesContentDirectory = Path.Combine(dResultsFullGroupDirectory, _Configuration.PropertyConfiguration.ResultContent); string d2FacePartsContentDirectory = Path.Combine(d2ResultsFullGroupDirectory, _Configuration.PropertyConfiguration.ResultContent); string d2FacePartsContentCollectionDirectory = Path.Combine(d2ResultsFullGroupDirectory, "[()]"); - if (distinctFilteredMappingCollection.Count > 0) + if (distinctValidImageMappingCollection.Count > 0) { Shared.Models.Stateless.Methods.IPath.ChangeDateForEmptyDirectories(d2FacePartsContentDirectory, ticks); if (Directory.Exists(d2FacePartsContentCollectionDirectory)) Shared.Models.Stateless.Methods.IPath.MakeHiddenIfAllItemsAreHidden(d2FacePartsContentCollectionDirectory); } if (Directory.Exists(fPhotoPrismContentDirectory)) - F_PhotoPrism.WriteMatches(fPhotoPrismContentDirectory, _Configuration.PersonBirthdayFormat, _Configuration.RectangleIntersectMinimums, ticks, distinctFilteredFaces, mapLogic); + F_PhotoPrism.WriteMatches(fPhotoPrismContentDirectory, _Configuration.PersonBirthdayFormat, _Configuration.RectangleIntersectMinimums, ticks, distinctValidImageFaces, mapLogic); if (_Configuration.SaveShortcutsForOutputResolutions.Contains(outputResolution)) - mapLogic.SaveShortcutsForOutputResolutionsDuringMapLogic(containers, personKeyToIds, dFacesContentDirectory, distinctFilteredMappingCollection); - ReadOnlyDictionary> idToWholePercentagesToMapping = Map.Models.Stateless.Methods.IMapLogic.GetIdToWholePercentagesToFace(distinctFilteredMappingCollection); + mapLogic.SaveShortcutsForOutputResolutionsDuringMapLogic(containers, personKeyToIds, dFacesContentDirectory, distinctValidImageMappingCollection); + ReadOnlyDictionary> idToWholePercentagesToMapping = Map.Models.Stateless.Methods.IMapLogic.GetIdToWholePercentagesToFace(distinctValidImageMappingCollection); if (_Configuration.SaveMappedForOutputResolutions.Contains(outputResolution)) - mapLogic.SaveMapped(dFacesContentDirectory, d2FacePartsContentDirectory, d2FacePartsContentCollectionDirectory, personKeyToIds, distinctFilteredMappingCollection, idToWholePercentagesToMapping); + mapLogic.SaveMapped(dFacesContentDirectory, d2FacePartsContentDirectory, d2FacePartsContentCollectionDirectory, personKeyToIds, distinctValidImageMappingCollection, idToWholePercentagesToMapping); if (_Configuration.SaveFaceDistancesForOutputResolutions.Contains(outputResolution)) - SaveFaceDistances(ticks, mapLogic, distinctFilteredFaces, dFacesContentDirectory, d2FacePartsContentDirectory, d2FacePartsContentCollectionDirectory, idToWholePercentagesToMapping); + SaveFaceDistances(ticks, mapLogic, distinctValidImageFaces, dFacesContentDirectory, d2FacePartsContentDirectory, d2FacePartsContentCollectionDirectory, idToWholePercentagesToMapping); } private bool? GetIsFocusModel(Shared.Models.Property? property) @@ -818,7 +975,7 @@ public partial class DlibDotNet _Logger?.LogInformation(string.Concat("Moved <", item.FilePath.FullName, '>')); } - private int GetNotMappedCountAndUpdateMappingFromPersonThenSetMapping(MapLogic mapLogic, Item item, bool? isFocusRelativePath, ReadOnlyCollection locationContainers, MappingFromItem mappingFromItem, List? mappingFromPhotoPrismCollection, List faces) + private int GetNotMappedCountAndUpdateMappingFromPersonThenSetMapping(MapLogic mapLogic, Item item, bool? isFocusRelativePath, MappingFromItem mappingFromItem, List? mappingFromPhotoPrismCollection, List faces) { int result; double? α; @@ -839,7 +996,6 @@ public partial class DlibDotNet bool? isFocusModel = GetIsFocusModel(item.Property); long[] jLinkResolvedPersonKeys = _JLinkResolvedDirectories.Select(l => l.PersonKey).ToArray(); ReadOnlyDictionary>? wholePercentagesToPersonContainers; - ReadOnlyCollection locationContainersFiles = new((from l in locationContainers select l.FilePath.FullName).ToArray()); foreach (Shared.Models.Face face in faces) { wholePercentagesToPersonContainers = mapLogic.GetWholePercentagesToPersonContainers(item.Property?.Id); @@ -880,36 +1036,37 @@ public partial class DlibDotNet return result; } - private static Map.Models.Configuration Get(Models.Configuration configuration, string facesFileNameExtension, string facesHiddenFileNameExtension, string facePartsFileNameExtension) + private static Map.Models.Configuration Get(Models.Configuration configuration, IDistanceLimits distanceLimits, string facesFileNameExtension, string facesHiddenFileNameExtension, string facePartsFileNameExtension) { - Map.Models.Configuration result = new( - configuration.DeletePossibleDuplicates, - configuration.DistanceMoveUnableToMatch, - configuration.DistanceRenameToMatch, - configuration.FaceConfidencePercent, - configuration.FaceDistancePermyriad, - configuration.LinkedAlpha, - configuration.LocationContainerDebugDirectory, - configuration.LocationContainerDirectoryPattern, - configuration.LocationContainerDistanceGroupMinimum, - configuration.LocationContainerDistanceTake, - configuration.LocationContainerDistanceTolerance, - configuration.LocationDigits, - configuration.MappingDefaultName, - configuration.PersonBirthdayFirstYear, - configuration.PersonBirthdayFormat, - configuration.PersonCharacters.ToArray(), - configuration.RangeDaysDeltaTolerance, - configuration.RangeDistanceTolerance, - configuration.ReMap, - configuration.SaveIndividually, - configuration.SaveSortingWithoutPerson, - configuration.SkipNotSkipDirectories, - configuration.SortingMaximumPerKey, - configuration.SortingMinimumToUseSigma, - facesFileNameExtension, - facesHiddenFileNameExtension, - facePartsFileNameExtension); + Map.Models.Configuration result = new(distanceLimits, + configuration.PropertyConfiguration, + configuration.DeletePossibleDuplicates, + configuration.DistanceMoveUnableToMatch, + configuration.DistanceRenameToMatch, + configuration.FaceConfidencePercent, + configuration.FaceDistancePermyriad, + configuration.LinkedAlpha, + configuration.LocationContainerDebugDirectory, + configuration.LocationContainerDirectoryPattern, + configuration.LocationContainerDistanceGroupMinimum, + configuration.LocationContainerDistanceTake, + configuration.LocationContainerDistanceTolerance, + configuration.LocationDigits, + configuration.MappingDefaultName, + configuration.PersonBirthdayFirstYear, + configuration.PersonBirthdayFormat, + configuration.PersonCharacters.ToArray(), + configuration.RangeDaysDeltaTolerance, + configuration.RangeDistanceTolerance, + configuration.ReMap, + configuration.SaveIndividually, + configuration.SaveSortingWithoutPerson, + configuration.SkipNotSkipDirectories, + configuration.SortingMaximumPerKey, + configuration.SortingMinimumToUseSigma, + facesFileNameExtension, + facesHiddenFileNameExtension, + facePartsFileNameExtension); return result; } @@ -962,7 +1119,6 @@ public partial class DlibDotNet string[] changesFrom = [nameof(A_Property)]; List> subFileTuples = []; FileHolder resizedFileHolder = _Resize.GetResizedFileHolder(cResultsFullGroupDirectory, item, outputResolutionHasNumber); - ReadOnlyCollection locationContainers = mapLogic.GetLocationContainers(item); if (item.Property is null || item.Property.Id is null || !item.SourceDirectoryFileHolder.Exists || item.SourceDirectoryFileHolder.CreationTime is null || item.SourceDirectoryFileHolder.LastWriteTime is null || item.Any()) { LogItemPropertyIsNull(item); @@ -999,12 +1155,30 @@ public partial class DlibDotNet _ = _BlurHasher.EncodeAndSave(item.FilePath, resizedFileHolder); } } + bool? shouldIgnore = property is null || property.Keywords is null ? null : _Configuration.PropertyConfiguration.IgnoreRulesKeyWords.Any(l => property.Keywords.Contains(l)); + if (shouldIgnore is not null) + { + if (shouldIgnore.Value) + { + FileInfo fileInfo = new(resizedFileHolder.FullName); + if (!fileInfo.Attributes.HasFlag(FileAttributes.Hidden)) + File.SetAttributes(resizedFileHolder.FullName, FileAttributes.Hidden); + } + if (resizedFileHolder.Exists && item.FilePath.HasIgnoreKeyword is not null && item.FilePath.HasIgnoreKeyword.Value != shouldIgnore.Value) + { + if (!item.FilePath.DirectoryName.Contains("Results") || !item.FilePath.DirectoryName.Contains("Resize")) + throw new NotSupportedException($"Rename File! <{item.FilePath.FileNameFirstSegment}>"); + else + { + File.Delete(resizedFileHolder.FullName); + } + } + } if (property is null || item.Property is null) throw new NullReferenceException(nameof(property)); item.SetResizedFileHolder(_Resize.FileNameExtension, resizedFileHolder); MappingFromItem mappingFromItem = IMappingFromItem.GetMappingFromItem(containerDateTimes, item, resizedFileHolder); ExifDirectory exifDirectory = metadata.GetMetadataCollection(item.FilePath, subFileTuples, parseExceptions, changesFrom, mappingFromItem); - Map.Models.Stateless.Methods.IMapLogic.SetCreationTimeMaybeMoveToDecade(_Configuration.PropertyConfiguration, _Configuration.MoveToDecade && _Configuration.LocationContainerDistanceTolerance is null, mappingFromItem, locationContainers); if (_AppSettings.Places.Count > 0) { float latitude; @@ -1029,7 +1203,12 @@ public partial class DlibDotNet } Dictionary outputResolutionToResize = _Resize.GetResizeKeyValuePairs(_Configuration.PropertyConfiguration, cResultsFullGroupDirectory, item.FilePath, subFileTuples, parseExceptions, item.Property, mappingFromItem); if (_Configuration.SaveResizedSubfiles) - _Resize.SaveResizedSubfile(_Configuration.PropertyConfiguration, outputResolution, cResultsFullGroupDirectory, subFileTuples, item, item.Property, mappingFromItem, outputResolutionToResize); + { + if (shouldIgnore is not null && item.FilePath.HasIgnoreKeyword is not null && item.FilePath.HasIgnoreKeyword.Value != shouldIgnore.Value) + faces = []; + else + _Resize.SaveResizedSubfile(_Configuration.PropertyConfiguration, outputResolution, cResultsFullGroupDirectory, subFileTuples, item, item.Property, mappingFromItem, outputResolutionToResize); + } if (!_Configuration.LoadOrCreateThenSaveImageFacesResultsForOutputResolutions.Contains(outputResolution)) faces = []; else if (!mappingFromItem.ResizedFileHolder.Exists && !File.Exists(mappingFromItem.ResizedFileHolder.FullName)) @@ -1039,15 +1218,23 @@ public partial class DlibDotNet List? mappingFromPhotoPrismCollection; if (!fileNameToCollection.TryGetValue(mappingFromItem.Id, out mappingFromPhotoPrismCollection)) mappingFromPhotoPrismCollection = null; - faces = _Faces.GetFaces(outputResolution, dResultsFullGroupDirectory, item.FilePath, subFileTuples, parseExceptions, property, mappingFromItem, outputResolutionToResize, locationContainers, mappingFromPhotoPrismCollection); - result = GetNotMappedCountAndUpdateMappingFromPersonThenSetMapping(mapLogic, item, isFocusRelativePath, locationContainers, mappingFromItem, mappingFromPhotoPrismCollection, faces); - List<(Shared.Models.Face, FileInfo?, string, bool Saved)> faceCollection = _Faces.SaveFaces(_FaceParts.FileNameExtension, dResultsFullGroupDirectory, item.FilePath, subFileTuples, parseExceptions, mappingFromItem, faces); + bool move = _Configuration.DistanceMoveUnableToMatch || _Configuration.DistanceRenameToMatch && _Configuration.LoadOrCreateThenSaveDistanceResultsForOutputResolutions.Contains(outputResolution); + faces = _Faces.GetFaces(outputResolution, cResultsFullGroupDirectory, dResultsFullGroupDirectory, item.FilePath, subFileTuples, parseExceptions, property, mappingFromItem, outputResolutionToResize, mappingFromPhotoPrismCollection); + result = GetNotMappedCountAndUpdateMappingFromPersonThenSetMapping(mapLogic, item, isFocusRelativePath, mappingFromItem, mappingFromPhotoPrismCollection, 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 ((_Configuration.DistanceMoveUnableToMatch || _Configuration.DistanceRenameToMatch) - && _Configuration.LoadOrCreateThenSaveDistanceResultsForOutputResolutions.Contains(outputResolution) - && locationContainers is not null && faceCollection.All(l => !l.Saved)) - _Distance.LookForMatchFacesAndPossiblyRename(_Faces.FileNameExtension, item.FilePath, mappingFromItem, faces, locationContainers); + if (move && faceCollection.All(l => !l.Saved)) + { + ReadOnlyCollection locationContainers = mapLogic.GetLocationContainers(item); + if (locationContainers.Count > 0) + { + 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(_Configuration.OverrideForFaceImages, _DistanceLimits, _Faces, item.FilePath, mappingFromItem, exifDirectory, faces, locationContainers); + } + } (bool review, int[] eyesCollection) = Shared.Models.Stateless.Methods.IFace.GetEyeCollection(_Configuration.EyeThreshold, faces); if (review || _Configuration.SaveFaceLandmarkForOutputResolutions.Contains(outputResolution)) { @@ -1076,7 +1263,7 @@ public partial class DlibDotNet List> sourceDirectoryChanges, Dictionary> fileNameToCollection, Container container, - Item[] filteredItems, + ReadOnlyCollection filteredItems, string message) { int result = 0; @@ -1088,8 +1275,8 @@ public partial class DlibDotNet bool? isFocusRelativePath = string.IsNullOrEmpty(_Configuration.FocusDirectory) ? null : container.SourceDirectory.StartsWith(focusRelativePath); string facePartsCollectionDirectory = _Configuration.CopyFacesAndSaveFaceLandmarkForOutputResolutions.Contains(outputResolution) ? _FaceParts.GetFacePartsDirectory(_Configuration.PropertyConfiguration, d2ResultsFullGroupDirectory, item: filteredItems.First(), includeNameWithoutExtension: false) : string.Empty; bool anyPropertiesChangedForX = _Configuration.PropertyConfiguration.PropertiesChangedForProperty || _Configuration.PropertiesChangedForDistance || _Configuration.PropertiesChangedForFaces || _Configuration.PropertiesChangedForIndex || _Configuration.PropertiesChangedForMetadata || _Configuration.PropertiesChangedForResize; - using ProgressBar progressBar = new(filteredItems.Length, message, options); - _ = Parallel.For(0, filteredItems.Length, parallelOptions, (i, state) => + using ProgressBar progressBar = new(filteredItems.Count, message, options); + _ = Parallel.For(0, filteredItems.Count, parallelOptions, (i, state) => { try { @@ -1115,7 +1302,7 @@ public partial class DlibDotNet catch (Exception) { exceptionsCount++; - if (exceptionsCount == filteredItems.Length) + if (exceptionsCount == filteredItems.Count) throw new Exception(string.Concat("All in [", container.SourceDirectory, "] failed!")); } }); @@ -1210,18 +1397,17 @@ public partial class DlibDotNet 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); } } } - private void SaveFaceDistances(long ticks, MapLogic mapLogic, ReadOnlyCollection distinctFilteredFaces, string dFacesContentDirectory, string d2FacePartsContentDirectory, string d2FacePartsContentCollectionDirectory, ReadOnlyDictionary> idToWholePercentagesToMapping) + private void SaveFaceDistances(long ticks, MapLogic mapLogic, ReadOnlyCollection distinctValidImageFaces, string dFacesContentDirectory, string d2FacePartsContentDirectory, string d2FacePartsContentCollectionDirectory, ReadOnlyDictionary> idToWholePercentagesToMapping) { - E_Distance.PreFilterSetFaceDistances(_AppSettings.MaxDegreeOfParallelism, ticks, distinctFilteredFaces); - ReadOnlyCollection faceDistanceContainers = E_Distance.GetFaceDistanceContainers(distinctFilteredFaces); + E_Distance.PreFilterSetFaceDistances(_AppSettings.MaxDegreeOfParallelism, _MapConfiguration, ticks, distinctValidImageFaces); + ReadOnlyCollection faceDistanceContainers = E_Distance.GetFaceDistanceContainers(distinctValidImageFaces); if (faceDistanceContainers.Count > 0) { List faceDistanceEncodings = []; @@ -1235,41 +1421,41 @@ public partial class DlibDotNet } } - private static void CheckForAllWindowsLinks(ReadOnlyCollection filesCollection) + private static void CheckForAllWindowsLinks(ReadOnlyCollection> filePathsCollection) { string fileFullPath; WindowsShortcut windowsShortcut; - foreach (string[] files in filesCollection) + foreach (ReadOnlyCollection filePaths in filePathsCollection) { - if (files.Length == 0) + if (filePaths.Count == 0) continue; - if (files.All(l => l.EndsWith(".lnk"))) + if (filePaths.All(l => l.ExtensionLowered == ".lnk")) { - foreach (string file in files) + foreach (FilePath filePath in filePaths) { - windowsShortcut = WindowsShortcut.Load(file); + windowsShortcut = WindowsShortcut.Load(filePath.FullName); if (windowsShortcut.Path is null) { - File.Delete(file); + File.Delete(filePath.FullName); continue; } fileFullPath = windowsShortcut.Path; windowsShortcut.Dispose(); - File.WriteAllText(Path.ChangeExtension(file, ".url"), fileFullPath); - File.Delete(file); + File.WriteAllText(Path.ChangeExtension(filePath.FullName, ".url"), fileFullPath); + File.Delete(filePath.FullName); } throw new NotSupportedException("All are Windows *.lnk files!"); } } } - private static bool IsFilesCollectionCountIsOne(ReadOnlyCollection filesCollection) + private static bool IsFilesCollectionCountIsOne(ReadOnlyCollection> filePathsCollection) { bool result = true; int count = 0; - foreach (string[] files in filesCollection) + foreach (ReadOnlyCollection filePaths in filePathsCollection) { - if (files.Length == 0) + if (filePaths.Count == 0) continue; count += 1; if (count > 1) @@ -1279,22 +1465,22 @@ public partial class DlibDotNet } } if (result) - CheckForAllWindowsLinks(filesCollection); + CheckForAllWindowsLinks(filePathsCollection); return result; } - private (string, ReadOnlyCollection, bool) GetFilesCollectionThenCopyOrMove(long ticks, string fileSearchFilter, string directorySearchFilter, ProgressBarOptions options, string outputResolution) + private (string, ReadOnlyCollection>, bool) GetFilesCollectionThenCopyOrMove(long ticks, string fileSearchFilter, string directorySearchFilter, ProgressBarOptions options, string outputResolution) { ProgressBar progressBar; string filesCollectionRootDirectory = _Configuration.PropertyConfiguration.RootDirectory; (string cResultsFullGroupDirectory, _, _, _) = GetResultsFullGroupDirectories(outputResolution); IReadOnlyDictionary fileGroups = Shared.Models.Stateless.Methods.IPath.GetKeyValuePairs(_Configuration.PropertyConfiguration, cResultsFullGroupDirectory, [_Configuration.PropertyConfiguration.ResultContent]); - ReadOnlyCollection filesCollection = IDirectory.GetFilesCollection(filesCollectionRootDirectory, directorySearchFilter, fileSearchFilter, useCeilingAverage: false); - int count = filesCollection.Select(l => l.Length).Sum(); - bool filesCollectionCountIsOne = IsFilesCollectionCountIsOne(filesCollection); + ReadOnlyCollection> filePathsCollection = IDirectory.GetFilePathCollections(_Configuration.PropertyConfiguration, filesCollectionRootDirectory, directorySearchFilter, fileSearchFilter, useCeilingAverage: false); + int count = filePathsCollection.Select(l => l.Count).Sum(); + bool filesCollectionCountIsOne = IsFilesCollectionCountIsOne(filePathsCollection); string message = $") Selecting for ## pattern directory - {(int)Math.Floor(new TimeSpan(DateTime.Now.Ticks - ticks).TotalSeconds)} total second(s)"; progressBar = new(count, message, options); - (string[] distinctDirectories, List<(FilePath, string)> toDoCollection) = IDirectory.GetToDoCollection(_Configuration.PropertyConfiguration, filesCollection, fileGroups[_Configuration.PropertyConfiguration.ResultContent], () => progressBar.Tick()); + (string[] distinctDirectories, List<(FilePath, string)> toDoCollection) = IDirectory.GetToDoCollection(_Configuration.PropertyConfiguration, filePathsCollection, fileGroups[_Configuration.PropertyConfiguration.ResultContent], () => progressBar.Tick()); progressBar.Dispose(); foreach (string distinctDirectory in distinctDirectories) { @@ -1305,7 +1491,7 @@ public partial class DlibDotNet progressBar = new(count, message, options); _ = IDirectory.CopyOrMove(toDoCollection, move: false, moveBack: false, () => progressBar.Tick()); progressBar.Dispose(); - return (filesCollectionRootDirectory, new(filesCollection), filesCollectionCountIsOne); + return (filesCollectionRootDirectory, filePathsCollection, filesCollectionCountIsOne); } } \ No newline at end of file diff --git a/Instance/Models/_F_Random.cs b/Instance/Models/_F_Random.cs index b3e1649..3e49789 100644 --- a/Instance/Models/_F_Random.cs +++ b/Instance/Models/_F_Random.cs @@ -1,5 +1,6 @@ using System.Collections.ObjectModel; using System.Text.Json; +using View_by_Distance.Shared.Models; namespace View_by_Distance.Instance.Models; @@ -24,14 +25,16 @@ internal class F_Random return result; } - private static ReadOnlyDictionary> GetDayToRelativePaths(ReadOnlyCollection mappingCollection, string dateFormat, ReadOnlyDictionary> idToPersonKeys) + private static ReadOnlyDictionary> GetDayToRelativePaths(ReadOnlyCollection distinctValidImageMappingCollection, string dateFormat, Dictionary immichAssets, ReadOnlyDictionary> idToPersonKeys) { Dictionary> results = []; string key; DateTime dateTime; List? personKeys; + ImmichAsset? immichAsset; List? relativePaths; - foreach (Shared.Models.Mapping mapping in mappingCollection) + bool immichAssetsCountIsZero = immichAssets.Count == 0; + foreach (Mapping mapping in distinctValidImageMappingCollection) { if (mapping.MappingFromItem.FilePath.DirectoryName is null || mapping.MappingFromPerson is null) continue; @@ -49,29 +52,55 @@ internal class F_Random if (!results.TryGetValue(key, out relativePaths)) throw new Exception(); } - relativePaths.Add(mapping.MappingFromItem.RelativePath); + if (immichAssetsCountIsZero) + relativePaths.Add(mapping.MappingFromItem.RelativePath); + else + { + if (!immichAssets.TryGetValue(mapping.MappingFromItem.RelativePath, out immichAsset)) + continue; + relativePaths.Add(immichAsset.PreviewPath); + } } return new(results); } - internal void Random(Property.Models.Configuration configuration, int radomUseBirthdayMinimum, string[] validKeyWordsToIgnoreInRandom, ReadOnlyDictionary> personKeyToIds, ReadOnlyCollection? notNineCollection, ReadOnlyCollection mappingCollection) + private static Dictionary GetImmichAssets(string immichAssetsFile) + { + Dictionary results = []; + if (!string.IsNullOrEmpty(immichAssetsFile) && File.Exists(immichAssetsFile)) + { + string json = File.ReadAllText(immichAssetsFile); + ImmichAsset[]? immichAssets = JsonSerializer.Deserialize(json, ImmichAssetCollectionSourceGenerationContext.Default.ImmichAssetArray); + if (immichAssets is not null) + { + foreach (ImmichAsset immichAsset in immichAssets) + results.Add(immichAsset.OriginalPath, immichAsset); + } + } + return results; + } + + internal void Random(Property.Models.Configuration configuration, string immichAssetsFile, int radomUseBirthdayMinimum, string[] validKeyWordsToIgnoreInRandom, ReadOnlyDictionary> personKeyToIds, ReadOnlyCollection? notNineCollection, ReadOnlyCollection distinctValidImageMappingCollection) { string key; string json; string jsonFile; Random random = new(); List? collection; + ImmichAsset? immichAsset; string dateFormat = "MM-dd"; List relativePaths = []; List distinctCollection = []; DateTime dateTime = new(2024, 1, 1); //Leap year + Dictionary immichAssets = GetImmichAssets(immichAssetsFile); ReadOnlyDictionary> idToPersonKeys = Map.Models.Stateless.Methods.IMapLogic.GetIdToPersonKeys(personKeyToIds); - ReadOnlyDictionary> dayToRelativePaths = GetDayToRelativePaths(mappingCollection, dateFormat, idToPersonKeys); + ReadOnlyDictionary> dayToRelativePaths = GetDayToRelativePaths(distinctValidImageMappingCollection, dateFormat, immichAssets, idToPersonKeys); string fRandomCollectionDirectory = Property.Models.Stateless.IResult.GetResultsDateGroupDirectory(configuration, nameof(F_Random), "[]"); string[] files = Directory.GetFiles(fRandomCollectionDirectory, "*", SearchOption.TopDirectoryOnly); foreach (string file in files) File.Delete(file); - foreach (Shared.Models.Mapping mapping in mappingCollection) + bool immichAssetsCountIsZero = immichAssets.Count == 0; + foreach (Mapping mapping in distinctValidImageMappingCollection) { if (distinctCollection.Contains(mapping.MappingFromItem.Id)) continue; @@ -81,7 +110,14 @@ internal class F_Random continue; if (mapping.MappingFromItem.Keywords is not null && mapping.MappingFromItem.Keywords.Any(l => validKeyWordsToIgnoreInRandom.Contains(l))) continue; - relativePaths.Add(mapping.MappingFromItem.RelativePath); + if (immichAssetsCountIsZero) + relativePaths.Add(mapping.MappingFromItem.RelativePath); + else + { + if (!immichAssets.TryGetValue(mapping.MappingFromItem.RelativePath, out immichAsset)) + continue; + relativePaths.Add(immichAsset.PreviewPath); + } distinctCollection.Add(mapping.MappingFromItem.Id); } if (relativePaths.Count > 0) diff --git a/Map/Models/Configuration.cs b/Map/Models/Configuration.cs index 96280f3..808f17d 100644 --- a/Map/Models/Configuration.cs +++ b/Map/Models/Configuration.cs @@ -1,6 +1,8 @@ namespace View_by_Distance.Map.Models; -public record Configuration(bool DeletePossibleDuplicates, +public record Configuration(Shared.Models.Methods.IDistanceLimits DistanceLimits, + Shared.Models.Properties.IPropertyConfiguration PropertyConfiguration, + bool DeletePossibleDuplicates, bool DistanceMoveUnableToMatch, bool DistanceRenameToMatch, int FaceConfidencePercent, diff --git a/Map/Models/MapLogic.cs b/Map/Models/MapLogic.cs index 17f7327..79c1676 100644 --- a/Map/Models/MapLogic.cs +++ b/Map/Models/MapLogic.cs @@ -23,6 +23,14 @@ public partial class MapLogic : Shared.Models.Methods.IMapLogic long? Ticks, string? PersonDirectory); + internal record FilteredOriginalImage(int Id, + FilePath FilePath, + int ApproximateYears, + string PersonKeyFormatted, + string Directory, + string PersonDirectory, + string CheckFile); + public void SaveContainers(int? updated, List saveContainers) { if (_Configuration is null) @@ -137,7 +145,7 @@ public partial class MapLogic : Shared.Models.Methods.IMapLogic } } - private List GetCollectionForSaveShortcutsForOutputResolutionsPreMapLogic(string eDistanceContentDirectory, ReadOnlyDictionary> personKeyToIds, ReadOnlyCollection mappingCollection) + private List GetCollectionForSaveShortcutsForOutputResolutionsPreMapLogic(string eDistanceContentDirectory, ReadOnlyDictionary> personKeyToIds, ReadOnlyCollection distinctValidImageMappingCollection) { List results = []; if (_Configuration is null) @@ -153,7 +161,7 @@ public partial class MapLogic : Shared.Models.Methods.IMapLogic string personKeyFormatted; Calendar calendar = new CultureInfo("en-US").Calendar; ReadOnlyDictionary> idToPersonKeys = IMapLogic.GetIdToPersonKeys(personKeyToIds); - foreach (Mapping mapping in mappingCollection) + foreach (Mapping mapping in distinctValidImageMappingCollection) { dateTime = mapping.MappingFromItem.GetDateTimeOriginalThenMinimumDateTime(); description = mapping.MappingFromLocation is null ? mapping.MappingFromItem.Id.ToString() : mapping.MappingFromLocation.DeterministicHashCodeKey; @@ -181,12 +189,12 @@ public partial class MapLogic : Shared.Models.Methods.IMapLogic personKeyFormatted = IPersonBirthday.GetFormatted(_Configuration.PersonBirthdayFormat, mapping.MappingFromPerson.PersonKey); directory = Path.Combine($"{eDistanceContentDirectory}---", "Person Key Shortcuts", personKeyFormatted, directoryName); fileName = Path.Combine(directory, $"{mapping.MappingFromItem.FilePath.Name}.lnk"); - results.Add(new(mapping.MappingFromItem.FilePath.FullName, directory, mapping.MappingFromItem.GetDateTimeOriginalThenMinimumDateTime(), fileName, description, MakeAllHidden: false)); + results.Add(new(mapping.MappingFromItem.FilePath.FullName, directory, dateTime, fileName, description, MakeAllHidden: false)); if (IPerson.IsDefaultName(mapping.MappingFromPerson)) continue; directory = Path.Combine($"{eDistanceContentDirectory}---", "Name Shortcuts", mapping.MappingFromPerson.DisplayDirectoryName, directoryName); fileName = Path.Combine(directory, $"{mapping.MappingFromItem.FilePath.Name}.lnk"); - results.Add(new(mapping.MappingFromItem.FilePath.FullName, directory, mapping.MappingFromItem.GetDateTimeOriginalThenMinimumDateTime(), fileName, description, MakeAllHidden: false)); + results.Add(new(mapping.MappingFromItem.FilePath.FullName, directory, dateTime, fileName, description, MakeAllHidden: false)); } return results; } @@ -230,7 +238,7 @@ public partial class MapLogic : Shared.Models.Methods.IMapLogic ReadOnlyDictionary readOnlyPersonKeyToCount; Dictionary> skipCollection = []; Dictionary> skipNotSkipCollection = []; - ReadOnlyDictionary> readOnlyskipCollection; + ReadOnlyDictionary> readOnlySkipCollection; string? rootDirectoryParent = Path.GetDirectoryName(propertyConfiguration.RootDirectory); string eDistanceContentTicksDirectory = Path.Combine(eDistanceContentDirectory, ticks.ToString()); ReadOnlyDictionary>> idThenWholePercentagesToPersonContainers; @@ -297,7 +305,7 @@ public partial class MapLogic : Shared.Models.Methods.IMapLogic skipCollection, readOnlyPersonKeyFormattedToPersonContainer, personKeyFormattedIdThenWholePercentagesCollection); - readOnlyskipCollection = new(skipCollection); + readOnlySkipCollection = new(skipCollection); notMappedPersonContainers.AddRange(Stateless.MapLogic.GetNotMappedPersonContainers(configuration, ticks, personContainers, @@ -307,7 +315,7 @@ public partial class MapLogic : Shared.Models.Methods.IMapLogic configuration, ticks, personContainers, - readOnlyskipCollection, + readOnlySkipCollection, records)); int lossCount = records.Count - locationContainers.Count; int unableToMatchCount = records.Count - personKeyFormattedIdThenWholePercentagesCollection.Count; @@ -351,6 +359,12 @@ public partial class MapLogic : Shared.Models.Methods.IMapLogic return new(results); } + public ReadOnlyDictionary GetPersonKeyToPersonContainer() + { + Dictionary results = []; + return new(results); + } + public ReadOnlyDictionary> GetPersonKeyToIds() { Dictionary> results = []; @@ -452,6 +466,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; @@ -504,7 +543,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; @@ -532,7 +594,7 @@ public partial class MapLogic : Shared.Models.Methods.IMapLogic return new(debugDirectory, directory, ticks, personDirectory); } - private List GetSaveContainers(string dFacesContentDirectory, string d2FacePartsContentDirectory, ReadOnlyCollection mappingCollection, ReadOnlyDictionary> idToWholePercentagesToMapping, ReadOnlyDictionary> personKeyToIds, int? useFiltersCounter, bool saveMapped, bool saveIndividually, bool sortingContainersAny) + private List GetSaveContainers(string dFacesContentDirectory, string d2FacePartsContentDirectory, ReadOnlyCollection distinctValidImageMappingCollection, ReadOnlyDictionary> idToWholePercentagesToMapping, ReadOnlyDictionary> personKeyToIds, int? useFiltersCounter, bool saveMapped, bool saveIndividually, bool sortingContainersAny) { if (_Configuration is null) throw new NullReferenceException(nameof(_Configuration)); @@ -557,7 +619,7 @@ public partial class MapLogic : Shared.Models.Methods.IMapLogic ReadOnlyDictionary? wholePercentagesToMapping; int padLeft = _Configuration.FaceDistancePermyriad.ToString().Length; string forceSingleImageHumanized = nameof(Shared.Models.Stateless.IMapLogic.ForceSingleImage).Humanize(LetterCasing.Title); - foreach (Mapping mapping in mappingCollection) + foreach (Mapping mapping in distinctValidImageMappingCollection) { if (mapping.MappingFromLocation is null) continue; @@ -586,7 +648,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; @@ -651,7 +713,7 @@ public partial class MapLogic : Shared.Models.Methods.IMapLogic return results; } - public void SaveMapped(string dFacesContentDirectory, string d2FacePartsContentDirectory, string d2FacePartsContentCollectionDirectory, ReadOnlyDictionary> personKeyToIds, ReadOnlyCollection mappingCollection, ReadOnlyDictionary> idToWholePercentagesToMapping) + public void SaveMapped(string dFacesContentDirectory, string d2FacePartsContentDirectory, string d2FacePartsContentCollectionDirectory, ReadOnlyDictionary> personKeyToIds, ReadOnlyCollection distinctValidImageMappingCollection, ReadOnlyDictionary> idToWholePercentagesToMapping) { if (_Configuration is null) throw new NullReferenceException(nameof(_Configuration)); @@ -659,7 +721,7 @@ public partial class MapLogic : Shared.Models.Methods.IMapLogic bool saveMapped = true; int? useFiltersCounter = null; string mappingDirectory = Path.Combine(_EDistanceContentTicksDirectory, nameof(Shared.Models.Stateless.IMapLogic.Mapping)); - List saveContainers = GetSaveContainers(dFacesContentDirectory, d2FacePartsContentDirectory, mappingCollection, idToWholePercentagesToMapping, personKeyToIds, useFiltersCounter, saveMapped, sortingContainersAny: true, saveIndividually: false); + List saveContainers = GetSaveContainers(dFacesContentDirectory, d2FacePartsContentDirectory, distinctValidImageMappingCollection, idToWholePercentagesToMapping, personKeyToIds, useFiltersCounter, saveMapped, sortingContainersAny: true, saveIndividually: false); SaveContainers(updated, saveContainers); if (!string.IsNullOrEmpty(_EDistanceContentTicksDirectory) && Directory.Exists(mappingDirectory)) Stateless.MapLogic.SaveMappingShortcuts(mappingDirectory); @@ -753,7 +815,7 @@ public partial class MapLogic : Shared.Models.Methods.IMapLogic keyToCount.Add(key, new()); _ = keyToCount.TryAdd(key, 0); keyToCount[key]++; - if (!_Configuration.SaveIndividually && keyToCount[key] < _Configuration.SortingMaximumPerKey) + if (!_Configuration.SaveIndividually && keyToCount[key] <= _Configuration.SortingMaximumPerKey) segmentC = null; else segmentC = sortingContainer.Sorting.DistancePermyriad.ToString(); @@ -764,6 +826,95 @@ public partial class MapLogic : Shared.Models.Methods.IMapLogic return result; } + private string? GetDisplayDirectoryName(string? displayDirectoryName, LocationContainer locationContainer, ReadOnlyDictionary personKeyToPersonContainer) + { + string? result = displayDirectoryName; + if (personKeyToPersonContainer.Count != 0) + { + 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, ReadOnlyDictionary onlyOne, ReadOnlyDictionary personKeyToPersonContainer) + { + 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; + bool isCounterPersonYear; + string facePartsDirectory; + FileHolder? faceFileHolder; + SaveContainer? saveContainer; + string? displayDirectoryName; + FileHolder? resizedFileHolder; + int? useFiltersCounter = null; + string resizeContentDirectory; + FileHolder? facePartsFileHolder; + FileHolder? hiddenFaceFileHolder; + LocationContainer locationContainer; + bool sortingContainersAny = onlyOne.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 (KeyValuePair keyValuePair in onlyOne) + { + if (_Configuration.SaveIndividually) + break; + locationContainer = keyValuePair.Value; + if (locationContainer.LengthPermyriad is null || locationContainer.LengthSource is null) + continue; + segmentB = locationContainer.LengthPermyriad.Value.ToString().PadLeft(2, '0')[..2]; + displayDirectoryName = GetDisplayDirectoryName(locationContainer.DisplayDirectoryName, locationContainer, personKeyToPersonContainer); + isCounterPersonYear = locationContainer.PersonKey is not null && IPersonBirthday.IsCounterPersonYear(locationContainer.PersonKey.Value); + (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); + } + 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) @@ -802,7 +953,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; @@ -1029,7 +1180,7 @@ public partial class MapLogic : Shared.Models.Methods.IMapLogic collection.Add(new(personBirthday.Value.Ticks, Path.Combine(checkDirectory, personKeyFormatted, fileNameWithoutExtension, $"{ids.Count} Face(s)"))); foreach ((long personKey, string displayDirectoryName) in collection) { - matches = (from l in personContainers where l.Key == personKey && l.ApproximateYears.HasValue select l).ToArray(); + matches = (from l in personContainers where l.Key == personKey select l).ToArray(); if (matches.Length == 0) continue; personKeyFormatted = IPersonBirthday.GetFormatted(_Configuration.PersonBirthdayFormat, personKey); @@ -1044,11 +1195,11 @@ public partial class MapLogic : Shared.Models.Methods.IMapLogic return results; } - private (int, FilePath, int, string, string, string, string)[] GetCollectionForSaveFilteredOriginalImagesFromJLinks(string[] jLinks, string a2PeopleContentDirectory, ReadOnlyCollection personContainers, ReadOnlyCollection mappingCollection, ReadOnlyDictionary> personKeyToIds) + private FilteredOriginalImage[] GetCollectionForSaveFilteredOriginalImagesFromJLinks(string[] jLinks, string a2PeopleContentDirectory, ReadOnlyCollection personContainers, ReadOnlyCollection distinctValidImageMappingCollection, ReadOnlyDictionary> personKeyToIds) { if (_Configuration is null) throw new NullReferenceException(nameof(_Configuration)); - (int, FilePath, int, string, string, string, string)[] results; + FilteredOriginalImage[] results; int count = 0; int group = 65; string checkFile; @@ -1058,9 +1209,9 @@ public partial class MapLogic : Shared.Models.Methods.IMapLogic string personKeyFormatted; List distinctCollection = []; bool usePersonKeyAndDeterministicHashCodeKey = false; + List filteredOriginalImages = []; List personKeyFormattedCollection = GetPersonKeyFormattedCollection(jLinks, a2PeopleContentDirectory, personContainers, personKeyToIds); - List<(int Id, FilePath FilePath, int ApproximateYears, string PersonKeyFormatted, string CheckFile, string Directory, string PersonDirectory)> collection = []; - foreach (Mapping mapping in mappingCollection) + foreach (Mapping mapping in distinctValidImageMappingCollection) { if (distinctCollection.Contains(mapping.MappingFromItem.Id)) continue; @@ -1069,7 +1220,7 @@ public partial class MapLogic : Shared.Models.Methods.IMapLogic throw new NotSupportedException(); if (mapping.By is null or Shared.Models.Stateless.IMapLogic.Sorting) continue; - if (mapping.MappingFromPerson?.ApproximateYears is null || mapping.MappingFromLocation is null) + if (mapping.MappingFromPerson is null || mapping.MappingFromLocation is null) continue; if (string.IsNullOrEmpty(mapping.MappingFromPerson.SegmentB)) throw new NotSupportedException(); @@ -1097,39 +1248,44 @@ public partial class MapLogic : Shared.Models.Methods.IMapLogic personDirectory = Path.Combine(directory, mapping.MappingFromPerson.DisplayDirectoryName); checkFile = Path.Combine(directory, $"{mapping.MappingFromLocation.DeterministicHashCodeKey}{mapping.MappingFromItem.FilePath.ExtensionLowered}"); } - collection.Add(new(mapping.MappingFromItem.Id, mapping.MappingFromItem.FilePath, mapping.MappingFromPerson.ApproximateYears.Value, personKeyFormatted, directory, personDirectory, checkFile)); + if (mapping is null) + continue; + if (mapping.MappingFromPerson.ApproximateYears is null) + filteredOriginalImages.Add(new(mapping.MappingFromItem.Id, mapping.MappingFromItem.FilePath, -1, personKeyFormatted, directory, personDirectory, checkFile)); + else + filteredOriginalImages.Add(new(mapping.MappingFromItem.Id, mapping.MappingFromItem.FilePath, mapping.MappingFromPerson.ApproximateYears.Value, personKeyFormatted, directory, personDirectory, checkFile)); distinctCollection.Add(mapping.MappingFromItem.Id); count += 1; } - results = (from l in collection orderby l.ApproximateYears descending, l.PersonKeyFormatted descending select l).ToArray(); + results = (from l in filteredOriginalImages orderby l.ApproximateYears descending, l.PersonKeyFormatted descending select l).ToArray(); return results; } - public void SaveFilteredOriginalImagesFromJLinks(string[] jLinks, ReadOnlyCollection personContainers, string a2PeopleContentDirectory, ReadOnlyDictionary> personKeyToIds, ReadOnlyCollection mappingCollection) + public void SaveFilteredOriginalImagesFromJLinks(string[] jLinks, ReadOnlyCollection personContainers, string a2PeopleContentDirectory, ReadOnlyDictionary> personKeyToIds, ReadOnlyCollection distinctValidImageMappingCollection) { if (_Configuration is null) throw new NullReferenceException(nameof(_Configuration)); FileHolder fileHolder; SaveContainer? saveContainer; List saveContainers = []; - (int, FilePath, int, string, string, string, string)[] collection = GetCollectionForSaveFilteredOriginalImagesFromJLinks(jLinks, a2PeopleContentDirectory, personContainers, mappingCollection, personKeyToIds); - foreach ((int id, FilePath filePath, int approximateYears, string personKeyFormatted, string directory, string personDirectory, string checkFile) in collection) + FilteredOriginalImage[] filteredOriginalImages = GetCollectionForSaveFilteredOriginalImagesFromJLinks(jLinks, a2PeopleContentDirectory, personContainers, distinctValidImageMappingCollection, personKeyToIds); + foreach (FilteredOriginalImage filteredOriginalImage in filteredOriginalImages) { - fileHolder = FileHolder.Get(filePath); - saveContainer = new(personDirectory); + fileHolder = FileHolder.Get(filteredOriginalImage.FilePath); + saveContainer = new(filteredOriginalImage.PersonDirectory); saveContainers.Add(saveContainer); - saveContainer = new(fileHolder, checkFile, directory); + saveContainer = new(fileHolder, filteredOriginalImage.CheckFile, filteredOriginalImage.Directory); saveContainers.Add(saveContainer); } SaveContainers(null, saveContainers); } - public void SaveShortcutsForOutputResolutionsPreMapLogic(string eDistanceContentDirectory, ReadOnlyDictionary> personKeyToIds, ReadOnlyCollection mappingCollection) + public void SaveShortcutsForOutputResolutionsPreMapLogic(string eDistanceContentDirectory, ReadOnlyDictionary> personKeyToIds, ReadOnlyCollection distinctValidImageMappingCollection) { string hiddenFile; WindowsShortcut windowsShortcut; List collection = []; - collection = GetCollectionForSaveShortcutsForOutputResolutionsPreMapLogic(eDistanceContentDirectory, personKeyToIds, mappingCollection); + collection = GetCollectionForSaveShortcutsForOutputResolutionsPreMapLogic(eDistanceContentDirectory, personKeyToIds, distinctValidImageMappingCollection); string[] distinctDirectories = (from l in collection select l.Directory).Distinct().ToArray(); foreach (string directory in distinctDirectories) { @@ -1163,7 +1319,7 @@ public partial class MapLogic : Shared.Models.Methods.IMapLogic } } - private (List<(string, DateTime[])>, List) GetCollectionForSaveShortcutsForOutputResolutionsDuringMapLogic(ReadOnlyDictionary> personKeyToIds, string dFacesContentDirectory, ReadOnlyCollection filteredItems, ReadOnlyCollection mappingCollection) + private (List<(string, DateTime[])>, List) GetCollectionForSaveShortcutsForOutputResolutionsDuringMapLogic(ReadOnlyDictionary> personKeyToIds, string dFacesContentDirectory, ReadOnlyCollection validImageItems, ReadOnlyCollection distinctValidImageMappingCollection) { if (_Configuration is null) throw new NullReferenceException(nameof(_Configuration)); @@ -1171,6 +1327,7 @@ public partial class MapLogic : Shared.Models.Methods.IMapLogic string fileName; string fullName; string directory; + DateTime dateTime; string facesDirectory; string? directoryName; string personDirectory; @@ -1178,7 +1335,7 @@ public partial class MapLogic : Shared.Models.Methods.IMapLogic List distinct = []; List collection = []; List<(string, DateTime[])> directoriesAndDateTimes = []; - foreach (Item item in filteredItems) + foreach (Item item in validImageItems) { if (item.ResizedFileHolder is null) continue; @@ -1204,13 +1361,14 @@ public partial class MapLogic : Shared.Models.Methods.IMapLogic } } } - foreach (Mapping mapping in mappingCollection) + foreach (Mapping mapping in distinctValidImageMappingCollection) { directoryName = Path.GetDirectoryName(mapping.MappingFromItem.RelativePath); if (directoryName is null) throw new NotSupportedException(); if (mapping.MappingFromItem.ResizedFileHolder.DirectoryName is null || !mapping.MappingFromItem.ResizedFileHolder.Exists) continue; + dateTime = mapping.MappingFromItem.GetDateTimeOriginalThenMinimumDateTime(); if (mapping.By is null or Shared.Models.Stateless.IMapLogic.Sorting || mapping.MappingFromPerson?.ApproximateYears is null) { if (mapping.MappingFromItem.ContainerDateTimes.Length > 0 && !distinct.Contains(mapping.MappingFromItem.ResizedFileHolder.DirectoryName)) @@ -1221,13 +1379,13 @@ public partial class MapLogic : Shared.Models.Methods.IMapLogic directory = Path.Combine(mapping.MappingFromItem.ResizedFileHolder.DirectoryName, $"{_PropertyConfiguration.ResultAllInOne}Shortcuts", _PropertyConfiguration.ResultAllInOne); personDirectory = Path.Combine(directory, "Unknown"); fileName = Path.Combine(personDirectory, $"{mapping.MappingFromItem.ResizedFileHolder.Name}.lnk"); - collection.Add(new(mapping.MappingFromItem.ResizedFileHolder.FullName, personDirectory, mapping.MappingFromItem.GetDateTimeOriginalThenMinimumDateTime(), fileName, mapping.MappingFromLocation?.DeterministicHashCodeKey, MakeAllHidden: false)); + collection.Add(new(mapping.MappingFromItem.ResizedFileHolder.FullName, personDirectory, dateTime, fileName, mapping.MappingFromLocation?.DeterministicHashCodeKey, MakeAllHidden: false)); facesDirectory = Stateless.MapLogic.GetFacesDirectory(_PropertyConfiguration, dFacesContentDirectory, mapping.FilePath, mapping.MappingFromItem); if (mapping.MappingFromLocation is null) continue; fullName = Path.Combine(facesDirectory, $"{mapping.MappingFromLocation.DeterministicHashCodeKey}{mapping.MappingFromItem.FilePath.ExtensionLowered}{_Configuration.FacesFileNameExtension}"); fileName = Path.Combine(personDirectory, $"{mapping.MappingFromLocation.DeterministicHashCodeKey}{mapping.MappingFromItem.ResizedFileHolder.ExtensionLowered}{_Configuration.FacesFileNameExtension}.lnk"); - collection.Add(new(fullName, personDirectory, mapping.MappingFromItem.GetDateTimeOriginalThenMinimumDateTime(), fileName, mapping.MappingFromLocation.DeterministicHashCodeKey, MakeAllHidden: true)); + collection.Add(new(fullName, personDirectory, dateTime, fileName, mapping.MappingFromLocation.DeterministicHashCodeKey, MakeAllHidden: true)); } else { @@ -1247,21 +1405,21 @@ public partial class MapLogic : Shared.Models.Methods.IMapLogic else personDirectory = Path.Combine(directory, mapping.MappingFromPerson.DisplayDirectoryName, $"{ids.Count} Face(s)"); fileName = Path.Combine(directory, $"{mapping.MappingFromItem.ResizedFileHolder.Name}.lnk"); - collection.Add(new(mapping.MappingFromItem.ResizedFileHolder.FullName, personDirectory, mapping.MappingFromItem.GetDateTimeOriginalThenMinimumDateTime(), fileName, mapping.MappingFromLocation?.DeterministicHashCodeKey, MakeAllHidden: false)); + collection.Add(new(mapping.MappingFromItem.ResizedFileHolder.FullName, personDirectory, dateTime, fileName, mapping.MappingFromLocation?.DeterministicHashCodeKey, MakeAllHidden: false)); } } return new(directoriesAndDateTimes, collection); } - public void SaveShortcutsForOutputResolutionsDuringMapLogic(ReadOnlyCollection containers, ReadOnlyDictionary> personKeyToIds, string dFacesContentDirectory, ReadOnlyCollection mappingCollection) + public void SaveShortcutsForOutputResolutionsDuringMapLogic(ReadOnlyCollection containers, ReadOnlyDictionary> personKeyToIds, string dFacesContentDirectory, ReadOnlyCollection distinctValidImageMappingCollection) { if (_Configuration is null) throw new NullReferenceException(nameof(_Configuration)); WindowsShortcut windowsShortcut; List<(string, DateTime[])> directoriesAndDateTimes; List collection; - ReadOnlyCollection filteredItems = IContainer.GetItems(_PropertyConfiguration, containers, distinctItems: true, filterItems: true); - (directoriesAndDateTimes, collection) = GetCollectionForSaveShortcutsForOutputResolutionsDuringMapLogic(personKeyToIds, dFacesContentDirectory, filteredItems, mappingCollection); + ReadOnlyCollection validImageItems = IContainer.GetValidImageItems(_PropertyConfiguration, containers, distinctItems: true, filterItems: true); + (directoriesAndDateTimes, collection) = GetCollectionForSaveShortcutsForOutputResolutionsDuringMapLogic(personKeyToIds, dFacesContentDirectory, validImageItems, distinctValidImageMappingCollection); string[] directories = (from l in collection select l.Directory).Distinct().ToArray(); foreach (string directory in directories) { @@ -1305,10 +1463,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; @@ -1316,7 +1477,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 { @@ -1340,37 +1501,46 @@ public partial class MapLogic : Shared.Models.Methods.IMapLogic return result; } - public void LookForAbandoned(Property.Models.Configuration propertyConfiguration, string bResultsFullGroupDirectory, ReadOnlyCollection readOnlyContainers, string cResultsFullGroupDirectory, string dResultsFullGroupDirectory, string d2ResultsFullGroupDirectory) + 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; string? directoryName; List distinctFilteredIds = IContainer.GetFilteredDistinctIds(propertyConfiguration, readOnlyContainers); LookForAbandoned(propertyConfiguration, distinctFilteredIds); - Stateless.LookForAbandonedLogic.LookForAbandoned(propertyConfiguration, bResultsFullGroupDirectory, distinctFilteredIds); + dlibDotNet.Tick(); + List distinctFilteredFileNameFirstSegments = IContainer.GetFilteredDistinctFileNameFirstSegments(propertyConfiguration, readOnlyContainers); + Stateless.LookForAbandonedLogic.LookForAbandoned(propertyConfiguration, bResultsFullGroupDirectory, distinctFilteredFileNameFirstSegments); + dlibDotNet.Tick(); directories = Directory.GetDirectories(cResultsFullGroupDirectory, "*", SearchOption.TopDirectoryOnly); foreach (string directory in directories) { directoryName = Path.GetFileName(directory); if (string.IsNullOrEmpty(directoryName) || directoryName.Length != 2 && directoryName.Length != 4) continue; - Stateless.LookForAbandonedLogic.LookForAbandoned(propertyConfiguration, distinctFilteredIds, directory, directoryName); + Stateless.LookForAbandonedLogic.LookForAbandoned(propertyConfiguration, distinctFilteredFileNameFirstSegments, directory, directoryName); } + dlibDotNet.Tick(); directories = Directory.GetDirectories(dResultsFullGroupDirectory, "*", SearchOption.TopDirectoryOnly); foreach (string directory in directories) { directoryName = Path.GetFileName(directory); if (string.IsNullOrEmpty(directoryName) || directoryName.Length != 2 && directoryName.Length != 4) continue; - Stateless.LookForAbandonedLogic.LookForAbandoned(propertyConfiguration, distinctFilteredIds, directory, directoryName); + Stateless.LookForAbandonedLogic.LookForAbandoned(propertyConfiguration, distinctFilteredFileNameFirstSegments, directory, directoryName); } + dlibDotNet.Tick(); directories = Directory.GetDirectories(d2ResultsFullGroupDirectory, "*", SearchOption.TopDirectoryOnly); foreach (string directory in directories) { directoryName = Path.GetFileName(directory); if (string.IsNullOrEmpty(directoryName) || directoryName.Length != 2 && directoryName.Length != 4) continue; - Stateless.LookForAbandonedLogic.LookForAbandoned(propertyConfiguration, distinctFilteredIds, directory, directoryName); + Stateless.LookForAbandonedLogic.LookForAbandoned(propertyConfiguration, distinctFilteredFileNameFirstSegments, directory, directoryName); } + dlibDotNet.Tick(); } } \ No newline at end of file diff --git a/Map/Models/Stateless/DecadeLogic.cs b/Map/Models/Stateless/DecadeLogic.cs index 936ef2d..5b7f99c 100644 --- a/Map/Models/Stateless/DecadeLogic.cs +++ b/Map/Models/Stateless/DecadeLogic.cs @@ -32,9 +32,23 @@ internal abstract class DecadeLogic return result; } - internal static void SetCreationTimeMaybeMoveToDecade(Property.Models.Configuration propertyConfiguration, bool moveToDecade, MappingFromItem mappingFromItem, ReadOnlyCollection locationContainers) + internal static void SetCreationTime(MappingFromItem mappingFromItem, ReadOnlyCollection locationContainers) { DateTime dateTime; + foreach (LocationContainer locationContainer in locationContainers) + { + dateTime = mappingFromItem.DateTimeOriginal is null ? mappingFromItem.MinimumDateTime : mappingFromItem.DateTimeOriginal.Value; + if (locationContainer.CreationDateOnly.Year != dateTime.Year || locationContainer.CreationDateOnly.Month != dateTime.Month || locationContainer.CreationDateOnly.Day != dateTime.Day) + { + if (!File.Exists(locationContainer.FilePath.FullName)) + continue; + File.SetCreationTime(locationContainer.FilePath.FullName, dateTime); + } + } + } + + internal static void MoveToDecade(Property.Models.Configuration propertyConfiguration, MappingFromItem mappingFromItem, ReadOnlyCollection locationContainers) + { string halfDecade; string checkDirectory; string? yearDirectory; @@ -44,13 +58,6 @@ internal abstract class DecadeLogic string? personKeyFormattedDirectoryName; foreach (LocationContainer locationContainer in locationContainers) { - if (!File.Exists(locationContainer.FilePath.FullName)) - continue; - dateTime = mappingFromItem.DateTimeOriginal is null ? mappingFromItem.MinimumDateTime : mappingFromItem.DateTimeOriginal.Value; - if (locationContainer.CreationDateOnly.Year != dateTime.Year || locationContainer.CreationDateOnly.Month != dateTime.Month || locationContainer.CreationDateOnly.Day != dateTime.Day) - File.SetCreationTime(locationContainer.FilePath.FullName, dateTime); - if (!moveToDecade) - continue; if (string.IsNullOrEmpty(locationContainer.FilePath.DirectoryName)) continue; personNameDirectoryName = Path.GetFileName(locationContainer.FilePath.DirectoryName); @@ -68,6 +75,8 @@ internal abstract class DecadeLogic if (halfDecade == yearDirectoryName) continue; checkDirectory = Path.Combine(personKeyFormattedDirectory, halfDecade, personNameDirectoryName); + if (!File.Exists(locationContainer.FilePath.FullName)) + continue; if (!Directory.Exists(checkDirectory)) _ = Directory.CreateDirectory(checkDirectory); File.Move(locationContainer.FilePath.FullName, Path.Combine(checkDirectory, locationContainer.FilePath.Name)); diff --git a/Map/Models/Stateless/DistanceLogic.cs b/Map/Models/Stateless/DistanceLogic.cs index ff418a8..5b1e341 100644 --- a/Map/Models/Stateless/DistanceLogic.cs +++ b/Map/Models/Stateless/DistanceLogic.cs @@ -250,14 +250,27 @@ internal abstract class DistanceLogic return results; } - private static void RenameUnknown(string[] files) + private static string[] RenameBirth(string[] files) { + List results = []; + string checkFile; foreach (string file in files) { - if (file.EndsWith(".unk")) + if (file.EndsWith(".brt")) + { + results.Add(file); continue; - File.Move(file, $"{file}.unk"); + } + checkFile = $"{file}.brt"; + if (File.Exists(checkFile)) + { + results.Add(file); + continue; + } + File.Move(file, checkFile); + results.Add(checkFile); } + return results.ToArray(); } private static void MovedToNewestPersonKeyFormatted(string personKeyFormatted, string newestPersonKeyFormatted, TicksDirectory ticksDirectory, string personKeyDirectory) @@ -374,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++; @@ -422,7 +435,7 @@ internal abstract class DistanceLogic if (!isDefault.Value) { if (personKeyFormattedToNewestPersonKeyFormatted.Count > 0 && newestPersonKeyFormatted is null) - RenameUnknown(files); + files = RenameBirth(files); else if (newestPersonKeyFormatted is not null && personKeyFormatted != newestPersonKeyFormatted) { if (!check) diff --git a/Map/Models/Stateless/FaceFileLogic.cs b/Map/Models/Stateless/FaceFileLogic.cs new file mode 100644 index 0000000..4b9bbee --- /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(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, Configuration configuration, IFaceD dFace, long ticks, 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(configuration, dFace, results, filePaths[i]); + }); + return results; + } + +} \ No newline at end of file diff --git a/Map/Models/Stateless/LookForAbandonedLogic.cs b/Map/Models/Stateless/LookForAbandonedLogic.cs index 2d16a3d..ed0921a 100644 --- a/Map/Models/Stateless/LookForAbandonedLogic.cs +++ b/Map/Models/Stateless/LookForAbandonedLogic.cs @@ -6,13 +6,12 @@ namespace View_by_Distance.Map.Models.Stateless; internal abstract class LookForAbandonedLogic { - internal static void LookForAbandoned(Property.Models.Configuration propertyConfiguration, List distinctFilteredIds, string directory, string directoryName) + internal static void LookForAbandoned(Property.Models.Configuration propertyConfiguration, List distinctFilteredFileNameFirstSegments, string directory, string directoryName) { FilePath filePath; FileHolder fileHolder; string fileNameFirstSegment; List renameCollection = []; - string[] distinctFilteredIdsValues = distinctFilteredIds.Select(l => l.ToString()).ToArray(); string[] files = Directory.GetFiles(directory, "*", SearchOption.AllDirectories); foreach (string file in files) { @@ -21,7 +20,7 @@ internal abstract class LookForAbandonedLogic fileNameFirstSegment = fileHolder.NameWithoutExtension.Split('.')[0]; if (!filePath.IsIntelligentIdFormat && filePath.SortOrder is null) continue; - if (distinctFilteredIdsValues.Contains(fileNameFirstSegment)) + if (distinctFilteredFileNameFirstSegments.Contains(fileNameFirstSegment)) continue; renameCollection.Add(file); } @@ -36,7 +35,7 @@ internal abstract class LookForAbandonedLogic } } - internal static void LookForAbandoned(Property.Models.Configuration propertyConfiguration, string bResultsFullGroupDirectory, List distinctFilteredIds) + internal static void LookForAbandoned(Property.Models.Configuration propertyConfiguration, string bResultsFullGroupDirectory, List distinctFilteredFileNameFirstSegments) { string[] directories = Directory.GetDirectories(bResultsFullGroupDirectory, "*", SearchOption.TopDirectoryOnly); foreach (string directory in directories) @@ -44,7 +43,7 @@ internal abstract class LookForAbandonedLogic string? directoryName = Path.GetFileName(directory); if (string.IsNullOrEmpty(directoryName) || (directoryName.Length != 2 && directoryName.Length != 4)) continue; - LookForAbandoned(propertyConfiguration, distinctFilteredIds, directory, directoryName); + LookForAbandoned(propertyConfiguration, distinctFilteredFileNameFirstSegments, directory, directoryName); } } diff --git a/Map/Models/Stateless/MapLogic.cs b/Map/Models/Stateless/MapLogic.cs index 837f75a..29495ab 100644 --- a/Map/Models/Stateless/MapLogic.cs +++ b/Map/Models/Stateless/MapLogic.cs @@ -106,8 +106,9 @@ internal abstract class MapLogic internal static string GetMappingSegmentB(long ticks, long personKey, int? approximateYears, MappingFromItem mappingFromItem) { string result; + DateTime dateTime = mappingFromItem.GetDateTimeOriginalThenMinimumDateTime(); PersonBirthday personBirthday = IPersonBirthday.GetPersonBirthday(personKey); - result = GetMappingSegmentB(ticks, personBirthday, approximateYears, mappingFromItem.GetDateTimeOriginalThenMinimumDateTime(), mappingFromItem.IsWrongYear); + result = GetMappingSegmentB(ticks, personBirthday, approximateYears, dateTime, mappingFromItem.IsWrongYear); return result; } @@ -178,7 +179,9 @@ internal abstract class MapLogic internal static string GetMappingSegmentB(long ticks, PersonBirthday personBirthday, int? approximateYears, MappingFromItem mappingFromItem) { - string result = GetMappingSegmentB(ticks, personBirthday, approximateYears, mappingFromItem.GetDateTimeOriginalThenMinimumDateTime(), mappingFromItem.IsWrongYear); + string result; + DateTime dateTime = mappingFromItem.GetDateTimeOriginalThenMinimumDateTime(); + result = GetMappingSegmentB(ticks, personBirthday, approximateYears, dateTime, mappingFromItem.IsWrongYear); return result; } @@ -322,7 +325,7 @@ internal abstract class MapLogic continue; } if (!personKeyFormattedToPersonContainer.TryGetValue(personKeyFormattedIdThenWholePercentages.PersonKeyFormatted, out personContainer)) - throw new Exception(); + continue; if (!results.TryGetValue(personKeyFormattedIdThenWholePercentages.Id, out idTo)) { results.Add(personKeyFormattedIdThenWholePercentages.Id, []); @@ -435,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; @@ -464,7 +467,7 @@ internal abstract class MapLogic results.RemoveAt(i); continue; } - if (!filePath.Name.EndsWith(".dup") && !filePath.Name.EndsWith(".unk") && !filePath.Name.EndsWith(".abd")) + if (!filePath.Name.EndsWith(".abd") && !filePath.Name.EndsWith(".brt") && !filePath.Name.EndsWith(".dup") && !filePath.Name.EndsWith(".unk")) continue; if (!File.Exists(filePath.FullName)) continue; @@ -537,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)); @@ -558,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)) { @@ -573,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)); @@ -727,7 +735,7 @@ internal abstract class MapLogic personDisplayDirectoryName = personKeyFormattedIdThenWholePercentages.PersonDisplayDirectoryName is null ? configuration.MappingDefaultName : personKeyFormattedIdThenWholePercentages.PersonDisplayDirectoryName; matches = configuration.PersonCharacters.Where(l => personDisplayDirectoryName.Contains(l)).ToArray(); if (matches.Length == 0) - throw new NotSupportedException(); + continue; group = IPerson.GetHourGroup(personKeyFormattedIdThenWholePercentages.PersonDisplayDirectoryName, personBirthday.Value.Hour); (status, sex, first) = IPerson.GetPersonHour(personKeyFormattedIdThenWholePercentages.PersonDisplayDirectoryName, personBirthday.Value.Hour); personDirectory = new(matches.First(), group, status, sex, first); @@ -987,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; @@ -1095,11 +1119,11 @@ internal abstract class MapLogic return new(results); } - internal static ReadOnlyDictionary> GetIdToWholePercentagesToFace(ReadOnlyCollection mappingCollection) + internal static ReadOnlyDictionary> GetIdToWholePercentagesToFace(ReadOnlyCollection distinctValidImageMappingCollection) { Dictionary> results = []; Dictionary? keyValuePairs; - foreach (Mapping mapping in mappingCollection) + foreach (Mapping mapping in distinctValidImageMappingCollection) { if (mapping.MappingFromLocation is null) continue; @@ -1168,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), @@ -1199,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; @@ -1269,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; @@ -1277,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 889f001..6c8cc2e 100644 --- a/Map/Models/Stateless/Methods/IMapLogic.cs +++ b/Map/Models/Stateless/Methods/IMapLogic.cs @@ -1,4 +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; @@ -10,44 +12,64 @@ public interface IMapLogic static ReadOnlyDictionary> GetIdToPersonKeys(ReadOnlyDictionary> personKeyToIds) => MapLogic.GetIdToPersonKeys(personKeyToIds); - ReadOnlyCollection TestStatic_GetFaces(ReadOnlyCollection items) => + ReadOnlyCollection TestStatic_GetFaces(ReadOnlyCollection items) => GetFaces(items); - static ReadOnlyCollection GetFaces(ReadOnlyCollection items) => + static ReadOnlyCollection GetFaces(ReadOnlyCollection items) => MapLogic.GetFaces(items); - Shared.Models.Mapping[] TestStatic_GetSelectedMappingCollection(ReadOnlyCollection items) => + Mapping[] TestStatic_GetSelectedMappingCollection(ReadOnlyCollection items) => GetSelectedMappingCollection(items); - static Shared.Models.Mapping[] GetSelectedMappingCollection(ReadOnlyCollection items) => + static Mapping[] GetSelectedMappingCollection(ReadOnlyCollection items) => MapLogic.GetSelectedMappingCollection(items); - Shared.Models.Mapping[] TestStatic_GetSelectedMappingCollection(ReadOnlyCollection faces) => + Mapping[] TestStatic_GetSelectedMappingCollection(ReadOnlyCollection faces) => GetSelectedMappingCollection(faces); - static Shared.Models.Mapping[] GetSelectedMappingCollection(ReadOnlyCollection faces) => + static Mapping[] GetSelectedMappingCollection(ReadOnlyCollection faces) => MapLogic.GetSelectedMappingCollection(faces); - ReadOnlyDictionary> TestStatic_GetIdToWholePercentagesToFace(ReadOnlyCollection mappingCollection) => - GetIdToWholePercentagesToFace(mappingCollection); - static ReadOnlyDictionary> GetIdToWholePercentagesToFace(ReadOnlyCollection mappingCollection) => - MapLogic.GetIdToWholePercentagesToFace(mappingCollection); + ReadOnlyDictionary> TestStatic_GetIdToWholePercentagesToFace(ReadOnlyCollection distinctValidImageMappingCollection) => + GetIdToWholePercentagesToFace(distinctValidImageMappingCollection); + static ReadOnlyDictionary> GetIdToWholePercentagesToFace(ReadOnlyCollection distinctValidImageMappingCollection) => + MapLogic.GetIdToWholePercentagesToFace(distinctValidImageMappingCollection); List<(string, long)> TestStatic_GetJLinkDirectories(string genealogicalDataCommunicationFile, string[] jLinks, string personBirthdayFormat, char[] personCharacters, string a2PeopleSingletonDirectory, string a2PeopleContentDirectory) => GetJLinkDirectories(genealogicalDataCommunicationFile, jLinks, personBirthdayFormat, personCharacters, a2PeopleSingletonDirectory, a2PeopleContentDirectory); static List<(string, long)> GetJLinkDirectories(string genealogicalDataCommunicationFile, string[] jLinks, string personBirthdayFormat, char[] personCharacters, string a2PeopleSingletonDirectory, string a2PeopleContentDirectory) => MapLogic.GetJLinkDirectories(genealogicalDataCommunicationFile, jLinks, personBirthdayFormat, personCharacters, a2PeopleSingletonDirectory, a2PeopleContentDirectory); - void TestStatic_SetCreationTimeMaybeMoveToDecade(Property.Models.Configuration propertyConfiguration, bool moveToDecade, Shared.Models.MappingFromItem mappingFromItem, ReadOnlyCollection locationContainers) => - SetCreationTimeMaybeMoveToDecade(propertyConfiguration, moveToDecade, mappingFromItem, locationContainers); - static void SetCreationTimeMaybeMoveToDecade(Property.Models.Configuration propertyConfiguration, bool moveToDecade, Shared.Models.MappingFromItem mappingFromItem, ReadOnlyCollection locationContainers) => - DecadeLogic.SetCreationTimeMaybeMoveToDecade(propertyConfiguration, moveToDecade, mappingFromItem, locationContainers); + void TestStatic_SetCreationTime(MappingFromItem mappingFromItem, ReadOnlyCollection locationContainers) => + SetCreationTime(mappingFromItem, locationContainers); + static void SetCreationTime(MappingFromItem mappingFromItem, ReadOnlyCollection locationContainers) => + DecadeLogic.SetCreationTime(mappingFromItem, locationContainers); - bool? TestStatic_CanReMap(long[] jLinkResolvedPersonKeys, ReadOnlyDictionary>? wholePercentagesToPersonContainers, Shared.Models.MappingFromLocation mappingFromLocation) => + void TestStatic_MoveToDecade(Property.Models.Configuration propertyConfiguration, MappingFromItem mappingFromItem, ReadOnlyCollection locationContainers) => + MoveToDecade(propertyConfiguration, mappingFromItem, locationContainers); + static void MoveToDecade(Property.Models.Configuration propertyConfiguration, MappingFromItem mappingFromItem, ReadOnlyCollection locationContainers) => + DecadeLogic.MoveToDecade(propertyConfiguration, mappingFromItem, locationContainers); + + bool? TestStatic_CanReMap(long[] jLinkResolvedPersonKeys, ReadOnlyDictionary>? wholePercentagesToPersonContainers, MappingFromLocation mappingFromLocation) => CanReMap(jLinkResolvedPersonKeys, wholePercentagesToPersonContainers, mappingFromLocation); - static bool? CanReMap(long[] jLinkResolvedPersonKeys, ReadOnlyDictionary>? wholePercentagesToPersonContainers, Shared.Models.MappingFromLocation mappingFromLocation) => - MapLogic.CanReMap(jLinkResolvedPersonKeys, wholePercentagesToPersonContainers, mappingFromLocation); + static bool? CanReMap(long[] jLinkResolvedPersonKeys, ReadOnlyDictionary>? wholePercentagesToPersonContainers, MappingFromLocation mappingFromLocation) => + MapLogic.CanReMap(jLinkResolvedPersonKeys, wholePercentagesToPersonContainers, mappingFromLocation.WholePercentages); - string TestStatic_GetDecade(Shared.Models.MappingFromItem mappingFromItem) => + 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(Shared.Models.MappingFromItem 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, Configuration configuration, IFaceD dFace, long ticks, ReadOnlyCollection filePaths) => + GetAvailable(maxDegreeOfParallelism, configuration, dFace, ticks, filePaths); + static List GetAvailable(int maxDegreeOfParallelism, Configuration configuration, IFaceD dFace, long ticks, ReadOnlyCollection filePaths) => + FaceFileLogic.GetAvailable(maxDegreeOfParallelism, configuration, dFace, ticks, filePaths); + } \ No newline at end of file diff --git a/Map/Models/Stateless/RelationLogic.cs b/Map/Models/Stateless/RelationLogic.cs index 25ad46f..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(); } @@ -254,7 +260,7 @@ internal abstract class RelationLogic _ = 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\", } } }"; + json = /*lang=json*/ """{ "[markdown]": { "editor.wordWrap": "off" }, "foam.links.hover.enable": false, "foam.graph.style": { "background": "#202020", "node": { "note": "#f2cb1d", "distance": "green", "image": "orange", "placeholder": "white", } } }"""; _ = 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\": [] } ] }"); _ = IPath.WriteAllText(Path.Combine(vsCodeDirectory, "tasks.json"), json, updateDateWhenMatches: false, compareBeforeWrite: true, updateToWhenMatches: null); @@ -422,7 +428,7 @@ internal abstract class RelationLogic if (!Directory.Exists(directory)) _ = Directory.CreateDirectory(directory); WriteVsCodeFiles(eDistanceContentDirectory, displayDirectoryName, directory); - relationContainers = distance.GetRelationContainers(configuration.FaceDistancePermyriad, configuration.LocationContainerDistanceTake, configuration.LocationContainerDistanceTolerance.Value, group.RelationContainersCollection); + relationContainers = distance.GetRelationContainers(configuration.DistanceLimits, configuration.FaceDistancePermyriad, configuration.LocationContainerDistanceTake, configuration.LocationContainerDistanceTolerance.Value, group.RelationContainersCollection); movedFiles = GetMoveFiles(configuration, group.Key, take, isCounterPersonYear, displayDirectoryName, relationContainers); WriteFile(take, group.PersonKey, isCounterPersonYear, personKeyFormatted, displayDirectoryName, directory, ticks, uri, relationContainers, movedFiles); } diff --git a/PhotoPrism/Models/_F_PhotoPrism.cs b/PhotoPrism/Models/_F_PhotoPrism.cs index b821be4..9b4d055 100644 --- a/PhotoPrism/Models/_F_PhotoPrism.cs +++ b/PhotoPrism/Models/_F_PhotoPrism.cs @@ -169,7 +169,7 @@ public class F_PhotoPrism } } - public static void WriteMatches(string fPhotoPrismContentDirectory, string personBirthdayFormat, float[] rectangleIntersectMinimums, long ticks, ReadOnlyCollection distinctFilteredFaces, Shared.Models.Methods.IMapLogic mapLogic) + public static void WriteMatches(string fPhotoPrismContentDirectory, string personBirthdayFormat, float[] rectangleIntersectMinimums, long ticks, ReadOnlyCollection distinctValidImageFaces, Shared.Models.Methods.IMapLogic mapLogic) { string file; string text; @@ -189,7 +189,7 @@ public class F_PhotoPrism ReadOnlyDictionary>? wholePercentagesToPersonContainers; (MappingFromPhotoPrism MappingFromPhotoPrism, Shared.Models.Marker Marker, float Percent)[] sortedCollection; List<(MappingFromPhotoPrism MappingFromPhotoPrism, Shared.Models.Marker Marker, float Percent)> collection = []; - foreach (Face face in distinctFilteredFaces) + foreach (Face face in distinctValidImageFaces) { collection.Clear(); wholePercentages = face.Mapping?.MappingFromLocation?.WholePercentages; diff --git a/Property/Models/A_Property.cs b/Property/Models/A_Property.cs index af31aea..4cc9d3f 100644 --- a/Property/Models/A_Property.cs +++ b/Property/Models/A_Property.cs @@ -209,7 +209,7 @@ public class A_Property SetAngleBracketCollection(aResultsFullGroupDirectory, sourceDirectory, anyNullOrNoIsUniqueFileName); } - private void SavePropertyParallelWork(int maxDegreeOfParallelism, Shared.Models.Methods.IMetadata metadata, List exceptions, List> sourceDirectoryChanges, Container container, List items, string message) + private void SavePropertyParallelWork(int maxDegreeOfParallelism, Shared.Models.Methods.IMetadata metadata, List exceptions, List> sourceDirectoryChanges, Container container, ReadOnlyCollection items, string message) { List> sourceDirectoryFileTuples = []; ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = maxDegreeOfParallelism }; diff --git a/Rename/Rename.cs b/Rename/Rename.cs index 6f7be64..6c3181e 100644 --- a/Rename/Rename.cs +++ b/Rename/Rename.cs @@ -402,7 +402,7 @@ public class Rename { if (record.Id is null) continue; - paddedId = IId.GetPaddedId(_PropertyConfiguration, record.Id.Value, ignore: null, record.Index); + paddedId = IId.GetPaddedId(_PropertyConfiguration, record.Id.Value, hasIgnoreKeyword: null, hasDateTimeOriginal: null, record.Index); checkFileExtension = fileHolder.ExtensionLowered == jpeg ? jpg : fileHolder.ExtensionLowered; checkFile = Path.Combine(seasonDirectory, $"{paddedId}{checkFileExtension}"); if (checkFile == fileHolder.FullName) diff --git a/Shared/Models/Container.cs b/Shared/Models/Container.cs index d3c5e56..8af98c8 100644 --- a/Shared/Models/Container.cs +++ b/Shared/Models/Container.cs @@ -1,8 +1,9 @@ +using System.Collections.ObjectModel; using System.Text.Json; namespace View_by_Distance.Shared.Models; -public record Container(string SourceDirectory, List Items) +public record Container(string SourceDirectory, ReadOnlyCollection Items) { public override string ToString() diff --git a/Shared/Models/FileHolder.cs b/Shared/Models/FileHolder.cs index 975583d..0bd86a3 100644 --- a/Shared/Models/FileHolder.cs +++ b/Shared/Models/FileHolder.cs @@ -20,33 +20,30 @@ public record FileHolder(DateTime? CreationTime, return result; } - public static FileHolder Get(FileInfo fileInfo) - { - FileHolder result; - if (!fileInfo.Exists) - result = new(null, - fileInfo.DirectoryName, - fileInfo.Exists, - fileInfo.Extension.ToLower(), - fileInfo.FullName, - null, - null, - fileInfo.Name, - Path.GetFileNameWithoutExtension(fileInfo.FullName)); - else - { - result = new(fileInfo.CreationTime, - fileInfo.DirectoryName, - fileInfo.Exists, - fileInfo.Extension.ToLower(), - fileInfo.FullName, - fileInfo.LastWriteTime, - fileInfo.Length, - fileInfo.Name, - Path.GetFileNameWithoutExtension(fileInfo.FullName)); - } - return result; - } + private static FileHolder GetExisting(FileInfo fileInfo) => + new(fileInfo.CreationTime, + fileInfo.DirectoryName, + fileInfo.Exists, + fileInfo.Extension.ToLower(), + fileInfo.FullName, + fileInfo.LastWriteTime, + fileInfo.Length, + fileInfo.Name, + Path.GetFileNameWithoutExtension(fileInfo.FullName)); + + private static FileHolder GetNonExisting(FileInfo fileInfo) => + new(null, + fileInfo.DirectoryName, + fileInfo.Exists, + fileInfo.Extension.ToLower(), + fileInfo.FullName, + null, + null, + fileInfo.Name, + Path.GetFileNameWithoutExtension(fileInfo.FullName)); + + public static FileHolder Get(FileInfo fileInfo) => + fileInfo.Exists ? GetExisting(fileInfo) : GetNonExisting(fileInfo); public static FileHolder Get(FilePath filePath) { diff --git a/Shared/Models/FilePath.cs b/Shared/Models/FilePath.cs index c562c6d..20c5b14 100644 --- a/Shared/Models/FilePath.cs +++ b/Shared/Models/FilePath.cs @@ -10,7 +10,8 @@ public record FilePath(long CreationTicks, string FileNameFirstSegment, string FullName, int? Id, - bool? IsIgnore, + bool? HasIgnoreKeyword, + bool? HasDateTimeOriginal, bool IsIntelligentIdFormat, long LastWriteTicks, long Length, @@ -42,7 +43,8 @@ public record FilePath(long CreationTicks, bool isIntelligentIdFormat = IId.NameWithoutExtensionIsIntelligentIdFormat(propertyConfiguration, fileNameFirstSegment); bool isPaddedIntelligentIdFormat = IId.NameWithoutExtensionIsPaddedIntelligentIdFormat(propertyConfiguration, sortOrderOnlyLengthIndex, fileNameFirstSegment); bool fileNameFirstSegmentIsIdFormat = !isPaddedIntelligentIdFormat && !isIntelligentIdFormat && IId.NameWithoutExtensionIsIdFormat(propertyConfiguration, fileHolder); - bool? isIgnore = !isIntelligentIdFormat && !isPaddedIntelligentIdFormat ? null : fileNameFirstSegment[^1] is '2' or '8'; + bool? hasIgnoreKeyword = !isIntelligentIdFormat && !isPaddedIntelligentIdFormat ? null : fileNameFirstSegment[^1] is '2' or '8'; + bool? hasDateTimeOriginal = !isIntelligentIdFormat && !isPaddedIntelligentIdFormat ? null : fileNameFirstSegment[^1] is '1' or '9'; if (!fileNameFirstSegmentIsIdFormat && !isIntelligentIdFormat && !isPaddedIntelligentIdFormat) (id, sortOder) = (null, null); else if (isIntelligentIdFormat) @@ -70,7 +72,8 @@ public record FilePath(long CreationTicks, fileNameFirstSegment, fileHolder.FullName, id, - isIgnore, + hasIgnoreKeyword, + hasDateTimeOriginal, isIntelligentIdFormat, fileHolder.LastWriteTime.Value.Ticks, fileHolder.Length.Value, 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/Methods/IDistance.cs b/Shared/Models/Methods/IDistance.cs index 1958ad1..d367ebb 100644 --- a/Shared/Models/Methods/IDistance.cs +++ b/Shared/Models/Methods/IDistance.cs @@ -5,6 +5,6 @@ namespace View_by_Distance.Shared.Models.Methods; public interface IDistance { - ReadOnlyCollection GetRelationContainers(int faceDistancePermyriad, int locationContainerDistanceTake, float locationContainerDistanceTolerance, ReadOnlyCollection locationContainers); + ReadOnlyCollection GetRelationContainers(IDistanceLimits distanceLimits, int faceDistancePermyriad, int locationContainerDistanceTake, float locationContainerDistanceTolerance, ReadOnlyCollection locationContainers); } \ No newline at end of file diff --git a/Shared/Models/Methods/IDistanceLimits.cs b/Shared/Models/Methods/IDistanceLimits.cs index 14247d0..8990fa3 100644 --- a/Shared/Models/Methods/IDistanceLimits.cs +++ b/Shared/Models/Methods/IDistanceLimits.cs @@ -9,6 +9,7 @@ public interface IDistanceLimits public double FaceDistancePermyriad { init; get; } public int SortingMaximumPerFaceShouldBeHigh { init; get; } public bool RangeDaysDeltaTargetLessThenUpper { init; get; } + public double RangeDistanceToleranceUpperLimit { init; get; } string GetCounts(); void AddCounts(int days, int distance); diff --git a/Shared/Models/Stateless/Methods/Container.cs b/Shared/Models/Stateless/Methods/Container.cs index 2fd5e52..8fd2294 100644 --- a/Shared/Models/Stateless/Methods/Container.cs +++ b/Shared/Models/Stateless/Methods/Container.cs @@ -8,7 +8,7 @@ internal abstract class Container private record FilePair(bool IsUnique, List Collection, FilePath FilePath, Models.Item Item) { } - internal static DateTime[] GetContainerDateTimes(IEnumerable items) + internal static DateTime[] GetContainerDateTimes(ReadOnlyCollection items) { DateTime[] results; long containerMinimumTicks = (from l in items select l.FilePath.LastWriteTicks).Min(); @@ -17,36 +17,38 @@ internal abstract class Container return results; } - internal static Models.Item[] GetFilterItems(Properties.IPropertyConfiguration propertyConfiguration, Models.Container container) + internal static ReadOnlyCollection GetValidImageItems(Properties.IPropertyConfiguration propertyConfiguration, Models.Container container) { List results = []; foreach (Models.Item item in container.Items) { - if (item.IsValidImageFormatExtension - && !propertyConfiguration.IgnoreExtensions.Contains(item.FilePath.ExtensionLowered)) - results.Add(item); + if (!item.IsValidImageFormatExtension || propertyConfiguration.IgnoreExtensions.Contains(item.FilePath.ExtensionLowered)) + continue; + results.Add(item); } - return results.ToArray(); + return container.Items.Count == results.Count ? container.Items : new(results); } - internal static List GetFilePairs(Properties.IPropertyConfiguration propertyConfiguration, string directorySearchFilter, string extension, string aPropertySingletonDirectory, ReadOnlyCollection filesCollection) + private static List GetFilePairs(Properties.IPropertyConfiguration propertyConfiguration, string directorySearchFilter, string extension, string aPropertySingletonDirectory, ReadOnlyCollection> filePathsCollection) { int renamed; const bool useCeilingAverage = true; List? filePairs = null; ReadOnlyCollection? jsonFilesCollection = null; IReadOnlyDictionary>? compareFileNamesToFiles = null; - IReadOnlyDictionary> fileNamesToFiles = XDirectory.GetFilesKeyValuePairs(filesCollection); - for (int i = 0; i < int.MaxValue; i++) + IReadOnlyDictionary> fileNamesToFiles = XDirectory.GetFilesKeyValuePairs(filePathsCollection); + for (int i = 0; i < short.MaxValue; i++) { renamed = 0; jsonFilesCollection = IDirectory.GetFilesCollection(aPropertySingletonDirectory, directorySearchFilter, extension, useCeilingAverage); compareFileNamesToFiles = XDirectory.GetFilesKeyValuePairs(jsonFilesCollection); renamed += XDirectory.LookForAbandoned(jsonFilesCollection, fileNamesToFiles, extension); - filePairs = XDirectory.GetFiles(filesCollection, fileNamesToFiles, extension, compareFileNamesToFiles); + filePairs = XDirectory.GetFiles(propertyConfiguration, filePathsCollection, fileNamesToFiles, extension, compareFileNamesToFiles); renamed += XDirectory.MaybeMove(propertyConfiguration, filePairs, aPropertySingletonDirectory, extension); if (renamed == 0) break; + if (i > 10) + throw new NotImplementedException(); } if (filePairs is null || jsonFilesCollection is null || compareFileNamesToFiles is null) throw new NullReferenceException(nameof(filePairs)); @@ -69,26 +71,26 @@ internal abstract class Container return result; } - private static void ParallelFor(Properties.IPropertyConfiguration propertyConfiguration, string aPropertySingletonDirectory, string extension, int rootDirectoryLength, Models.FilePair filePair, List results) + private static void ParallelFor(IDlibDotNet? dlibDotNet, Properties.IPropertyConfiguration propertyConfiguration, string aPropertySingletonDirectory, string extension, int rootDirectoryLength, Models.FilePair filePair, List results) { + dlibDotNet?.Tick(); bool abandoned = false; Models.FileHolder sourceDirectoryFileHolder; Models.Property? property = GetProperty(filePair); - Models.FileHolder imageFileInfo = IFileHolder.Get(filePair.Path); - FilePath filePath = FilePath.Get(propertyConfiguration, imageFileInfo, index: null); + Models.FileHolder imageFileHolder = IFileHolder.Get(filePair.Path); + FilePath filePath = FilePath.Get(propertyConfiguration, imageFileHolder, index: null); bool? fileSizeChanged = property is not null ? property.FileSize != filePath.Length : null; bool isValidImageFormatExtension = propertyConfiguration.ValidImageFormatExtensions.Contains(filePath.ExtensionLowered); - if (property is not null && property.Keywords is not null) + bool? shouldIgnore = property is null || property.Keywords is null ? null : propertyConfiguration.IgnoreRulesKeyWords.Any(l => property.Keywords.Contains(l)); + if (shouldIgnore is not null) { - if (filePath.IsIgnore is null) - throw new NullReferenceException(); - bool shouldIgnore = propertyConfiguration.IgnoreRulesKeyWords.Any(l => property.Keywords.Contains(l)); - if (shouldIgnore) + if (shouldIgnore.Value) { - if (shouldIgnore) - { } + FileInfo fileInfo = new(filePath.FullName); + if (!fileInfo.Attributes.HasFlag(FileAttributes.Hidden)) + File.SetAttributes(imageFileHolder.FullName, FileAttributes.Hidden); } - if (filePath.IsIgnore.Value != shouldIgnore) + if (filePath.HasIgnoreKeyword is not null && filePath.HasIgnoreKeyword.Value != shouldIgnore.Value) { if (filePath.DirectoryName.Contains("Results") && filePath.DirectoryName.Contains("Resize")) File.Delete(filePath.FullName); @@ -118,31 +120,31 @@ internal abstract class Container results.Add(new(filePair.IsUnique, filePair.Collection, filePath, item)); } - private static List GetFilePairs(Properties.IPropertyConfiguration propertyConfiguration, string aPropertySingletonDirectory, string filesCollectionDirectory, ReadOnlyCollection filesCollection, string directorySearchFilter) + private static List GetFilePairs(IDlibDotNet? dlibDotNet, Properties.IPropertyConfiguration propertyConfiguration, string aPropertySingletonDirectory, string filesCollectionDirectory, ReadOnlyCollection> filePathsCollection, string directorySearchFilter) { List results = []; const string extension = ".json"; int maxDegreeOfParallelism = Environment.ProcessorCount; int filesCollectionDirectoryLength = filesCollectionDirectory.Length; ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = maxDegreeOfParallelism }; - List filePairs = GetFilePairs(propertyConfiguration, directorySearchFilter, extension, aPropertySingletonDirectory, filesCollection); - _ = Parallel.For(0, filePairs.Count, parallelOptions, (i, state) => ParallelFor(propertyConfiguration, aPropertySingletonDirectory, extension, filesCollectionDirectoryLength, filePairs[i], results)); + List filePairs = GetFilePairs(propertyConfiguration, directorySearchFilter, extension, aPropertySingletonDirectory, filePathsCollection); + _ = Parallel.For(0, filePairs.Count, parallelOptions, (i, state) => ParallelFor(dlibDotNet, propertyConfiguration, aPropertySingletonDirectory, extension, filesCollectionDirectoryLength, filePairs[i], results)); return results; } - private static (int, Models.Container[]) GetContainers(Properties.IPropertyConfiguration propertyConfiguration, string aPropertySingletonDirectory, string filesCollectionDirectory, ReadOnlyCollection filesCollection, string directorySearchFilter) + private static (int, Models.Container[]) GetContainers(IDlibDotNet? dlibDotNet, Properties.IPropertyConfiguration propertyConfiguration, string aPropertySingletonDirectory, string filesCollectionDirectory, ReadOnlyCollection> filePathsCollection, string directorySearchFilter) { List results = []; - string? directory; + string directory; List? items; Models.Container container; List directories = []; Dictionary> directoryToItems = []; - foreach (string[] files in filesCollection) + foreach (ReadOnlyCollection filePaths in filePathsCollection) { - if (files.Length == 0) + if (filePaths.Count == 0) continue; - directory = Path.GetDirectoryName(files.First()); + directory = filePaths[0].DirectoryName; if (directory is null) continue; if (!directories.Contains(directory)) @@ -154,7 +156,7 @@ internal abstract class Container throw new Exception(); } } - List filePairs = GetFilePairs(propertyConfiguration, aPropertySingletonDirectory, filesCollectionDirectory, filesCollection, directorySearchFilter); + List filePairs = GetFilePairs(dlibDotNet, propertyConfiguration, aPropertySingletonDirectory, filesCollectionDirectory, filePathsCollection, directorySearchFilter); foreach (FilePair filePair in filePairs) { if (!directoryToItems.TryGetValue(filePair.FilePath.DirectoryName, out items)) @@ -169,43 +171,44 @@ internal abstract class Container { if (keyValuePair.Value.Count == 0) continue; - container = new(keyValuePair.Key, keyValuePair.Value); + container = new(keyValuePair.Key, new(keyValuePair.Value)); results.Add(container); } return (filePairs.Count, results.ToArray()); } - internal static (int, Models.Container[]) GetContainers(Properties.IPropertyConfiguration propertyConfiguration, string aPropertySingletonDirectory, string filesCollectionDirectory, ReadOnlyCollection filesCollection) + internal static ReadOnlyCollection GetContainers(IDlibDotNet dlibDotNet, Properties.IPropertyConfiguration propertyConfiguration, string aPropertySingletonDirectory, string filesCollectionDirectory, ReadOnlyCollection> filePathsCollection) { - int count; Models.Container[] results; const string directorySearchFilter = "*"; - (count, results) = GetContainers(propertyConfiguration, aPropertySingletonDirectory, filesCollectionDirectory, filesCollection, directorySearchFilter); - return (count, results); + (_, results) = GetContainers(dlibDotNet, propertyConfiguration, aPropertySingletonDirectory, filesCollectionDirectory, filePathsCollection, directorySearchFilter); + return new(results); } internal static (int, Models.Container[]) GetContainers(Properties.IPropertyConfiguration propertyConfiguration, string aPropertySingletonDirectory) { int count; Models.Container[] results; + IDlibDotNet? dlibDotNet = null; const bool useCeilingAverage = true; const string fileSearchFilter = "*"; const string directorySearchFilter = "*"; ReadOnlyCollection filesCollection = IDirectory.GetFilesCollection(propertyConfiguration.RootDirectory, directorySearchFilter, fileSearchFilter, useCeilingAverage); - (count, results) = GetContainers(propertyConfiguration, aPropertySingletonDirectory, propertyConfiguration.RootDirectory, filesCollection, directorySearchFilter); + ReadOnlyCollection> filePathsCollection = IDirectory.GetFilePathCollections(propertyConfiguration, filesCollection); + (count, results) = GetContainers(dlibDotNet, propertyConfiguration, aPropertySingletonDirectory, propertyConfiguration.RootDirectory, filePathsCollection, directorySearchFilter); return (count, results); } internal static List GetFilteredDistinctIds(Properties.IPropertyConfiguration propertyConfiguration, ReadOnlyCollection readOnlyContainers) { List results = []; - Models.Item[] filteredItems; + ReadOnlyCollection filteredItems; foreach (Models.Container container in readOnlyContainers) { if (container.Items.Count == 0) continue; - filteredItems = GetFilterItems(propertyConfiguration, container); - if (filteredItems.Length == 0) + filteredItems = GetValidImageItems(propertyConfiguration, container); + if (filteredItems.Count == 0) continue; foreach (Models.Item item in filteredItems) { @@ -219,11 +222,34 @@ internal abstract class Container return results; } - internal static ReadOnlyCollection GetItems(Properties.IPropertyConfiguration propertyConfiguration, ReadOnlyCollection containers, bool distinctItems, bool filterItems) + internal static List GetFilteredDistinctFileNameFirstSegments(Properties.IPropertyConfiguration propertyConfiguration, ReadOnlyCollection readOnlyContainers) + { + List results = []; + ReadOnlyCollection filteredItems; + foreach (Models.Container container in readOnlyContainers) + { + if (container.Items.Count == 0) + continue; + filteredItems = GetValidImageItems(propertyConfiguration, container); + if (filteredItems.Count == 0) + continue; + foreach (Models.Item item in filteredItems) + { + if (item.Property?.Id is null || item.ResizedFileHolder is null) + continue; + if (results.Contains(item.FilePath.FileNameFirstSegment)) + continue; + results.Add(item.FilePath.FileNameFirstSegment); + } + } + return results; + } + + internal static ReadOnlyCollection GetValidImageItems(Properties.IPropertyConfiguration propertyConfiguration, ReadOnlyCollection containers, bool distinctItems, bool filterItems) { List results = []; List distinct = []; - IEnumerable filteredItems; + ReadOnlyCollection filteredItems; foreach (Models.Container container in containers) { if (container.Items.Count == 0) @@ -232,8 +258,8 @@ internal abstract class Container filteredItems = container.Items; else { - filteredItems = GetFilterItems(propertyConfiguration, container); - if (!filteredItems.Any()) + filteredItems = GetValidImageItems(propertyConfiguration, container); + if (filteredItems.Count == 0) continue; } foreach (Models.Item item in filteredItems) diff --git a/Shared/Models/Stateless/Methods/IContainer.cs b/Shared/Models/Stateless/Methods/IContainer.cs index 2f8a42d..f727e3c 100644 --- a/Shared/Models/Stateless/Methods/IContainer.cs +++ b/Shared/Models/Stateless/Methods/IContainer.cs @@ -5,34 +5,39 @@ namespace View_by_Distance.Shared.Models.Stateless.Methods; public interface IContainer { - DateTime[] TestStatic_GetContainerDateTimes(IEnumerable items) => + DateTime[] TestStatic_GetContainerDateTimes(ReadOnlyCollection items) => GetContainerDateTimes(items); - static DateTime[] GetContainerDateTimes(IEnumerable items) => + static DateTime[] GetContainerDateTimes(ReadOnlyCollection items) => Container.GetContainerDateTimes(items); - Models.Item[] TestStatic_GetFilterItems(Properties.IPropertyConfiguration propertyConfiguration, Models.Container container) => - GetFilterItems(propertyConfiguration, container); - static Models.Item[] GetFilterItems(Properties.IPropertyConfiguration propertyConfiguration, Models.Container container) => - Container.GetFilterItems(propertyConfiguration, container); + ReadOnlyCollection TestStatic_GetValidImageItems(Properties.IPropertyConfiguration propertyConfiguration, Models.Container container) => + GetValidImageItems(propertyConfiguration, container); + static ReadOnlyCollection GetValidImageItems(Properties.IPropertyConfiguration propertyConfiguration, Models.Container container) => + Container.GetValidImageItems(propertyConfiguration, container); (int, Models.Container[]) TestStatic_GetContainers(Properties.IPropertyConfiguration propertyConfiguration, string aPropertySingletonDirectory) => GetContainers(propertyConfiguration, aPropertySingletonDirectory); static (int, Models.Container[]) GetContainers(Properties.IPropertyConfiguration propertyConfiguration, string aPropertySingletonDirectory) => Container.GetContainers(propertyConfiguration, aPropertySingletonDirectory); - (int, Models.Container[]) TestStatic_GetContainers(Properties.IPropertyConfiguration propertyConfiguration, string aPropertySingletonDirectory, string filesCollectionDirectory, ReadOnlyCollection filesCollection) => - GetContainers(propertyConfiguration, aPropertySingletonDirectory, filesCollectionDirectory, filesCollection); - static (int, Models.Container[]) GetContainers(Properties.IPropertyConfiguration propertyConfiguration, string aPropertySingletonDirectory, string filesCollectionDirectory, ReadOnlyCollection filesCollection) => - Container.GetContainers(propertyConfiguration, aPropertySingletonDirectory, filesCollectionDirectory, filesCollection); + ReadOnlyCollection TestStatic_GetContainers(IDlibDotNet dlibDotNet, Properties.IPropertyConfiguration propertyConfiguration, string aPropertySingletonDirectory, string filesCollectionDirectory, ReadOnlyCollection> filePathsCollection) => + GetContainers(dlibDotNet, propertyConfiguration, aPropertySingletonDirectory, filesCollectionDirectory, filePathsCollection); + static ReadOnlyCollection GetContainers(IDlibDotNet dlibDotNet, Properties.IPropertyConfiguration propertyConfiguration, string aPropertySingletonDirectory, string filesCollectionDirectory, ReadOnlyCollection> filePathsCollection) => + Container.GetContainers(dlibDotNet, propertyConfiguration, aPropertySingletonDirectory, filesCollectionDirectory, filePathsCollection); List TestStatic_GetFilteredDistinctIds(Properties.IPropertyConfiguration propertyConfiguration, ReadOnlyCollection readOnlyContainers) => GetFilteredDistinctIds(propertyConfiguration, readOnlyContainers); static List GetFilteredDistinctIds(Properties.IPropertyConfiguration propertyConfiguration, ReadOnlyCollection readOnlyContainers) => Container.GetFilteredDistinctIds(propertyConfiguration, readOnlyContainers); - ReadOnlyCollection TestStatic_GetItems(Properties.IPropertyConfiguration propertyConfiguration, ReadOnlyCollection containers, bool distinctItems, bool filterItems) => - GetItems(propertyConfiguration, containers, distinctItems, filterItems); - static ReadOnlyCollection GetItems(Properties.IPropertyConfiguration propertyConfiguration, ReadOnlyCollection containers, bool distinctItems, bool filterItems) => - Container.GetItems(propertyConfiguration, containers, distinctItems, filterItems); + List TestStatic_GetFilteredDistinctFileNameFirstSegments(Properties.IPropertyConfiguration propertyConfiguration, ReadOnlyCollection readOnlyContainers) => + GetFilteredDistinctFileNameFirstSegments(propertyConfiguration, readOnlyContainers); + static List GetFilteredDistinctFileNameFirstSegments(Properties.IPropertyConfiguration propertyConfiguration, ReadOnlyCollection readOnlyContainers) => + Container.GetFilteredDistinctFileNameFirstSegments(propertyConfiguration, readOnlyContainers); + + ReadOnlyCollection TestStatic_GetValidImageItems(Properties.IPropertyConfiguration propertyConfiguration, ReadOnlyCollection containers, bool distinctItems, bool filterItems) => + GetValidImageItems(propertyConfiguration, containers, distinctItems, filterItems); + static ReadOnlyCollection GetValidImageItems(Properties.IPropertyConfiguration propertyConfiguration, ReadOnlyCollection containers, bool distinctItems, bool filterItems) => + Container.GetValidImageItems(propertyConfiguration, containers, distinctItems, filterItems); } \ No newline at end of file diff --git a/Shared/Models/Stateless/Methods/IDirectory.cs b/Shared/Models/Stateless/Methods/IDirectory.cs index a4934ab..77d3ff2 100644 --- a/Shared/Models/Stateless/Methods/IDirectory.cs +++ b/Shared/Models/Stateless/Methods/IDirectory.cs @@ -20,24 +20,34 @@ public interface IDirectory static ReadOnlyCollection GetFilesCollection(string directory, string directorySearchFilter, string fileSearchFilter, bool useCeilingAverage) => XDirectory.GetFilesCollection(directory, directorySearchFilter, fileSearchFilter, useCeilingAverage); + ReadOnlyCollection> TestStatic_GetFilePathCollections(Properties.IPropertyConfiguration propertyConfiguration, string directory, string directorySearchFilter, string fileSearchFilter, bool useCeilingAverage) => + GetFilePathCollections(propertyConfiguration, directory, directorySearchFilter, fileSearchFilter, useCeilingAverage); + static ReadOnlyCollection> GetFilePathCollections(Properties.IPropertyConfiguration propertyConfiguration, string directory, string directorySearchFilter, string fileSearchFilter, bool useCeilingAverage) => + XDirectory.GetFilePathCollections(propertyConfiguration, directory, directorySearchFilter, fileSearchFilter, useCeilingAverage); + void TestStatic_MoveFiles(List files, string find, string replace) => MoveFiles(files, find, replace); static void MoveFiles(List files, string find, string replace) => XDirectory.MoveFiles(files, find, replace); - (string[], List<(FilePath, string)>) TestStatic_GetToDoCollection(Properties.IPropertyConfiguration propertyConfiguration, ReadOnlyCollection filesCollection, string[] directories, Action? tick) => - GetToDoCollection(propertyConfiguration, filesCollection, directories, tick); - static (string[], List<(FilePath, string)>) GetToDoCollection(Properties.IPropertyConfiguration propertyConfiguration, ReadOnlyCollection filesCollection, string[] directories, Action? tick) => - XDirectory.GetToDoCollection(propertyConfiguration, copyDuplicates: false, ifCanUseId: true, filesCollection, directories, tick); + (string[], List<(FilePath, string)>) TestStatic_GetToDoCollection(Properties.IPropertyConfiguration propertyConfiguration, ReadOnlyCollection> filePathsCollection, string[] directories, Action? tick) => + GetToDoCollection(propertyConfiguration, filePathsCollection, directories, tick); + static (string[], List<(FilePath, string)>) GetToDoCollection(Properties.IPropertyConfiguration propertyConfiguration, ReadOnlyCollection> filePathsCollection, string[] directories, Action? tick) => + XDirectory.GetToDoCollection(propertyConfiguration, copyDuplicates: false, ifCanUseId: true, filePathsCollection, directories, tick); - (string[], List<(FilePath, string)>) TestStatic_GetToDoCollection(Properties.IPropertyConfiguration propertyConfiguration, bool copyDuplicates, bool ifCanUseId, ReadOnlyCollection filesCollection, string[] directories, Action? tick) => - GetToDoCollection(propertyConfiguration, copyDuplicates, ifCanUseId, filesCollection, directories, tick); - static (string[], List<(FilePath, string)>) GetToDoCollection(Properties.IPropertyConfiguration propertyConfiguration, bool copyDuplicates, bool ifCanUseId, ReadOnlyCollection filesCollection, string[] directories, Action? tick) => - XDirectory.GetToDoCollection(propertyConfiguration, copyDuplicates, ifCanUseId, filesCollection, directories, tick); + (string[], List<(FilePath, string)>) TestStatic_GetToDoCollection(Properties.IPropertyConfiguration propertyConfiguration, bool copyDuplicates, bool ifCanUseId, ReadOnlyCollection> filePathsCollection, string[] directories, Action? tick) => + GetToDoCollection(propertyConfiguration, copyDuplicates, ifCanUseId, filePathsCollection, directories, tick); + static (string[], List<(FilePath, string)>) GetToDoCollection(Properties.IPropertyConfiguration propertyConfiguration, bool copyDuplicates, bool ifCanUseId, ReadOnlyCollection> filePathsCollection, string[] directories, Action? tick) => + XDirectory.GetToDoCollection(propertyConfiguration, copyDuplicates, ifCanUseId, filePathsCollection, directories, tick); List TestStatic_CopyOrMove(List<(FilePath, string)> toDoCollection, bool move, bool moveBack, Action? tick) => CopyOrMove(toDoCollection, move, moveBack, tick); static List CopyOrMove(List<(FilePath, string)> toDoCollection, bool move, bool moveBack, Action? tick) => XDirectory.CopyOrMove(toDoCollection, move, moveBack, tick); + ReadOnlyCollection> TestStatic_GetFilePathCollections(Properties.IPropertyConfiguration propertyConfiguration, ReadOnlyCollection filesCollection) => + GetFilePathCollections(propertyConfiguration, filesCollection); + static ReadOnlyCollection> GetFilePathCollections(Properties.IPropertyConfiguration propertyConfiguration, ReadOnlyCollection filesCollection) => + XDirectory.GetFilePathCollections(propertyConfiguration, filesCollection); + } \ No newline at end of file diff --git a/Shared/Models/Stateless/Methods/IDlibDotNet.cs b/Shared/Models/Stateless/Methods/IDlibDotNet.cs new file mode 100644 index 0000000..ac13a76 --- /dev/null +++ b/Shared/Models/Stateless/Methods/IDlibDotNet.cs @@ -0,0 +1,8 @@ +namespace View_by_Distance.Shared.Models.Stateless.Methods; + +public interface IDlibDotNet +{ + + void Tick(); + +} \ No newline at end of file diff --git a/Shared/Models/Stateless/Methods/IFaceD.cs b/Shared/Models/Stateless/Methods/IFaceD.cs new file mode 100644 index 0000000..f0963e6 --- /dev/null +++ b/Shared/Models/Stateless/Methods/IFaceD.cs @@ -0,0 +1,9 @@ +namespace View_by_Distance.Shared.Models.Stateless.Methods; + +public interface IFaceD +{ + + public string FileNameExtension { get; } + void ReSaveFace(ExifDirectory exifDirectory, FilePath filePath, Models.Face face, bool mappedFile); + +} \ No newline at end of file diff --git a/Shared/Models/Stateless/Methods/IFileHolder.cs b/Shared/Models/Stateless/Methods/IFileHolder.cs index 7bac89f..feb539e 100644 --- a/Shared/Models/Stateless/Methods/IFileHolder.cs +++ b/Shared/Models/Stateless/Methods/IFileHolder.cs @@ -28,6 +28,11 @@ public interface IFileHolder static Models.FileHolder Get(string fileName) => Models.FileHolder.Get(new FileInfo(fileName)); + Models.FileHolder TestStatic_Get(FileInfo fileInfo) => + Get(fileInfo); + static Models.FileHolder Get(FileInfo fileInfo) => + Models.FileHolder.Get(fileInfo); + Models.FileHolder TestStatic_Get(FilePath filePath) => Get(filePath); static Models.FileHolder Get(FilePath filePath) => diff --git a/Shared/Models/Stateless/Methods/IId.cs b/Shared/Models/Stateless/Methods/IId.cs index 7d05948..64668fd 100644 --- a/Shared/Models/Stateless/Methods/IId.cs +++ b/Shared/Models/Stateless/Methods/IId.cs @@ -3,20 +3,25 @@ namespace View_by_Distance.Shared.Models.Stateless.Methods; public interface IId { // ... - string TestStatic_GetIntelligentId(Properties.IPropertyConfiguration propertyConfiguration, long id, bool? ignore) => - GetIntelligentId(propertyConfiguration, id, ignore); - static string GetIntelligentId(Properties.IPropertyConfiguration propertyConfiguration, long id, bool? ignore) => - Id.GetIntelligentId(propertyConfiguration, id, ignore); + const int DeterministicHashCode = 9876543; + + static bool IsOffsetDeterministicHashCode(Properties.IPropertyConfiguration propertyConfiguration) => + propertyConfiguration.Offset == DeterministicHashCode; + + string TestStatic_GetIntelligentId(Properties.IPropertyConfiguration propertyConfiguration, long id, bool? hasIgnoreKeyword, bool? hasDateTimeOriginal) => + GetIntelligentId(propertyConfiguration, id, hasIgnoreKeyword, hasDateTimeOriginal); + static string GetIntelligentId(Properties.IPropertyConfiguration propertyConfiguration, long id, bool? hasIgnoreKeyword, bool? hasDateTimeOriginal) => + Id.GetIntelligentId(propertyConfiguration, id, hasIgnoreKeyword, hasDateTimeOriginal); int TestStatic_GetId(Properties.IPropertyConfiguration propertyConfiguration, string intelligentId) => GetId(propertyConfiguration, intelligentId); static int GetId(Properties.IPropertyConfiguration propertyConfiguration, string intelligentId) => Id.GetId(propertyConfiguration, intelligentId); - string TestStatic_GetPaddedId(Properties.IPropertyConfiguration propertyConfiguration, int id, bool? ignore, int? index) => - GetPaddedId(propertyConfiguration, id, ignore, index); - static string GetPaddedId(Properties.IPropertyConfiguration propertyConfiguration, int id, bool? ignore, int? index) => - Id.GetPaddedId(propertyConfiguration, id, ignore, index); + string TestStatic_GetPaddedId(Properties.IPropertyConfiguration propertyConfiguration, int id, bool? hasIgnoreKeyword, bool? hasDateTimeOriginal, int? index) => + GetPaddedId(propertyConfiguration, id, hasIgnoreKeyword, hasDateTimeOriginal, index); + static string GetPaddedId(Properties.IPropertyConfiguration propertyConfiguration, int id, bool? hasIgnoreKeyword, bool? hasDateTimeOriginal, int? index) => + Id.GetPaddedId(propertyConfiguration, id, hasIgnoreKeyword, hasDateTimeOriginal, index); string TestStatic_GetIgnoreFullPath(FilePath filePath, Models.FileHolder fileHolder) => GetIgnoreFullPath(filePath, fileHolder); diff --git a/Shared/Models/Stateless/Methods/ILocation.cs b/Shared/Models/Stateless/Methods/ILocation.cs index a9dca6e..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) => @@ -25,10 +25,10 @@ public interface ILocation static Models.Location? GetLocation(DatabaseFile databaseFile, Marker marker, Models.OutputResolution outputResolution) => Location.GetLocation(databaseFile, marker, outputResolution); - List TestStatic_GetLocations(List locationContainers, List faces, List mappingFromPhotoPrismCollection, float rectangleIntersectMinimum) => - GetLocations(locationContainers, faces, mappingFromPhotoPrismCollection, rectangleIntersectMinimum); - static List GetLocations(List locationContainers, List faces, List mappingFromPhotoPrismCollection, float rectangleIntersectMinimum) => - Location.GetLocations(locationContainers, faces, mappingFromPhotoPrismCollection, rectangleIntersectMinimum); + List TestStatic_GetLocations(List faces, List mappingFromPhotoPrismCollection, float rectangleIntersectMinimum) => + GetLocations(faces, mappingFromPhotoPrismCollection, rectangleIntersectMinimum); + static List GetLocations(List faces, List mappingFromPhotoPrismCollection, float rectangleIntersectMinimum) => + Location.GetLocations(faces, mappingFromPhotoPrismCollection, rectangleIntersectMinimum); RectangleF? TestStatic_GetPercentagesRectangle(int locationDigits, int wholePercentages) => GetPercentagesRectangle(locationDigits, wholePercentages); diff --git a/Shared/Models/Stateless/Methods/IPersonContainer.cs b/Shared/Models/Stateless/Methods/IPersonContainer.cs index 18fc592..d6074fa 100644 --- a/Shared/Models/Stateless/Methods/IPersonContainer.cs +++ b/Shared/Models/Stateless/Methods/IPersonContainer.cs @@ -1,3 +1,5 @@ +using System.Collections.ObjectModel; + namespace View_by_Distance.Shared.Models.Stateless.Methods; public interface IPersonContainer @@ -5,9 +7,9 @@ public interface IPersonContainer // ... - List TestStatic_GetPersonKeys(IEnumerable personContainers) => + List TestStatic_GetPersonKeys(ReadOnlyCollection personContainers) => GetPersonKeys(personContainers); - static List GetPersonKeys(IEnumerable personContainers) => + static List GetPersonKeys(ReadOnlyCollection personContainers) => PersonContainer.GetPersonKeys(personContainers); List TestStatic_GetPersonContainers(string a2PeopleSingletonDirectory, string personBirthdayFormat, char[] personCharacters, Properties.IPropertyConfiguration propertyConfiguration, string facesFileNameExtension) => diff --git a/Shared/Models/Stateless/Methods/IProperty.cs b/Shared/Models/Stateless/Methods/IProperty.cs index bcb8456..47e96ef 100644 --- a/Shared/Models/Stateless/Methods/IProperty.cs +++ b/Shared/Models/Stateless/Methods/IProperty.cs @@ -43,9 +43,9 @@ public interface IProperty static List GetDateTimes(Models.Property property) => Property.GetDateTimes(property.CreationTime, property.LastWriteTime, property.DateTime, property.DateTimeDigitized, property.DateTimeFromName, property.DateTimeOriginal, property.GPSDateStamp); - double TestStatic_GetStandardDeviation(IEnumerable values, double average) => + double TestStatic_GetStandardDeviation(List values, double average) => GetStandardDeviation(values, average); - static double GetStandardDeviation(IEnumerable values, double average) => + static double GetStandardDeviation(List values, double average) => Property.GetStandardDeviation(values, average); TimeSpan TestStatic_GetThreeStandardDeviationHigh(int minimum, Models.Container container) => diff --git a/Shared/Models/Stateless/Methods/Id.cs b/Shared/Models/Stateless/Methods/Id.cs index 948cfae..be19d22 100644 --- a/Shared/Models/Stateless/Methods/Id.cs +++ b/Shared/Models/Stateless/Methods/Id.cs @@ -35,23 +35,25 @@ internal abstract class Id return result; } - internal static string GetIntelligentId(Properties.IPropertyConfiguration propertyConfiguration, long id, bool? ignore) + internal static string GetIntelligentId(Properties.IPropertyConfiguration propertyConfiguration, long id, bool? hasIgnoreKeyword, bool? hasDateTimeOriginal) { string result; StringBuilder stringBuilder = new(); if (propertyConfiguration.IntMinValueLength < (propertyConfiguration.ResultAllInOneSubdirectoryLength + 2)) throw new NotSupportedException(); + if (hasDateTimeOriginal is null) + { } int key; string value; List resultAllInOneSubdirectoryChars = []; if (id > -1) { - key = ignore is not null && ignore.Value ? 8 : 9; + key = hasIgnoreKeyword is not null && hasIgnoreKeyword.Value ? 8 : 9; value = id.ToString().PadLeft(propertyConfiguration.IntMinValueLength, '0'); } else { - key = ignore is not null && ignore.Value ? 2 : 1; + key = hasIgnoreKeyword is not null && hasIgnoreKeyword.Value ? 2 : 1; value = id.ToString()[1..].PadLeft(propertyConfiguration.IntMinValueLength, '0'); } for (int i = value.Length - propertyConfiguration.ResultAllInOneSubdirectoryLength - 1; i > -1; i--) @@ -62,18 +64,18 @@ internal abstract class Id return result; } - internal static string GetPaddedId(Properties.IPropertyConfiguration propertyConfiguration, int id, bool? ignore, int? index) + internal static string GetPaddedId(Properties.IPropertyConfiguration propertyConfiguration, int id, bool? hasIgnoreKeyword, bool? hasDateTimeOriginal, int? index) { string result; if (propertyConfiguration.Offset < 0) result = Guid.NewGuid().ToString(); else { - string intelligentId = GetIntelligentId(propertyConfiguration, id, ignore); + string intelligentId = GetIntelligentId(propertyConfiguration, id, hasIgnoreKeyword, hasDateTimeOriginal); int check = GetId(propertyConfiguration, intelligentId); if (check != id) throw new NotSupportedException(); - result = index is null || propertyConfiguration.Offset == 9876543 ? intelligentId : $"{propertyConfiguration.Offset + index}{intelligentId}"; + result = index is null || propertyConfiguration.Offset == IId.DeterministicHashCode ? intelligentId : $"{propertyConfiguration.Offset + index}{intelligentId}"; } return result; } diff --git a/Shared/Models/Stateless/Methods/Location.cs b/Shared/Models/Stateless/Methods/Location.cs index c00c484..2ee5801 100644 --- a/Shared/Models/Stateless/Methods/Location.cs +++ b/Shared/Models/Stateless/Methods/Location.cs @@ -245,7 +245,7 @@ internal abstract class Location return result; } - internal static List GetLocations(List locationContainers, List faces, List mappingFromPhotoPrismCollection, float rectangleIntersectMinimum) + internal static List GetLocations(List faces, List mappingFromPhotoPrismCollection, float rectangleIntersectMinimum) { List results = []; bool any; @@ -266,12 +266,6 @@ internal abstract class Location outputResolution ??= face.OutputResolution; } int before = results.Count; - foreach (LocationContainer locationContainer in locationContainers) - { - if (locationContainer.Location is null) - continue; - results.Add(locationContainer.Location); - } foreach (MappingFromPhotoPrism mappingFromPhotoPrism in mappingFromPhotoPrismCollection) { if (outputResolution is null) @@ -289,19 +283,6 @@ internal abstract class Location location = GetLocation(mappingFromPhotoPrism.DatabaseFile, marker, prismRectangle.Value); if (location is null) break; - foreach (LocationContainer locationContainer in locationContainers) - { - if (any) - continue; - if (locationContainer.Rectangle is null) - continue; - percent = GetIntersectPercent(prismRectangle.Value, prismArea, locationContainer.Rectangle.Value); - if (percent is null || percent < rectangleIntersectMinimum) - continue; - if (!any) - any = true; - break; - } foreach (Models.Face face in faces) { if (any) @@ -329,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; diff --git a/Shared/Models/Stateless/Methods/PersonContainer.cs b/Shared/Models/Stateless/Methods/PersonContainer.cs index 39827d8..6349744 100644 --- a/Shared/Models/Stateless/Methods/PersonContainer.cs +++ b/Shared/Models/Stateless/Methods/PersonContainer.cs @@ -1,3 +1,5 @@ +using System.Collections.ObjectModel; + namespace View_by_Distance.Shared.Models.Stateless.Methods; internal abstract class PersonContainer @@ -313,7 +315,7 @@ internal abstract class PersonContainer return results; } - internal static List GetPersonKeys(IEnumerable personContainers) + internal static List GetPersonKeys(ReadOnlyCollection personContainers) { List results = []; long personKey; diff --git a/Shared/Models/Stateless/Methods/Property.cs b/Shared/Models/Stateless/Methods/Property.cs index 2fcb7f2..6cbb570 100644 --- a/Shared/Models/Stateless/Methods/Property.cs +++ b/Shared/Models/Stateless/Methods/Property.cs @@ -166,13 +166,13 @@ internal abstract class Property return result; } - internal static double GetStandardDeviation(IEnumerable values, double average) + internal static double GetStandardDeviation(List values, double average) { double result = 0; - if (!values.Any()) + if (values.Count == 0) throw new Exception("Collection must have at least one value!"); double sum = values.Sum(l => (l - average) * (l - average)); - result = Math.Sqrt(sum / values.Count()); + result = Math.Sqrt(sum / values.Count); return result; } diff --git a/Shared/Models/Stateless/Methods/XDirectory.cs b/Shared/Models/Stateless/Methods/XDirectory.cs index 41f5275..90b8901 100644 --- a/Shared/Models/Stateless/Methods/XDirectory.cs +++ b/Shared/Models/Stateless/Methods/XDirectory.cs @@ -54,6 +54,14 @@ internal abstract partial class XDirectory return new(results); } + internal static ReadOnlyCollection> GetFilePathCollections(Properties.IPropertyConfiguration propertyConfiguration, string directory, string directorySearchFilter, string fileSearchFilter, bool useCeilingAverage) + { + ReadOnlyCollection> results; + ReadOnlyCollection filesCollection = GetFilesCollection(directory, directorySearchFilter, fileSearchFilter, useCeilingAverage); + results = IDirectory.GetFilePathCollections(propertyConfiguration, filesCollection); + return results; + } + internal static IReadOnlyDictionary> GetFilesKeyValuePairs(ReadOnlyCollection filesCollection) { Dictionary> results = []; @@ -76,6 +84,26 @@ internal abstract partial class XDirectory return results; } + internal static IReadOnlyDictionary> GetFilesKeyValuePairs(ReadOnlyCollection> filePathsCollection) + { + Dictionary> results = []; + List? collection; + foreach (ReadOnlyCollection filePaths in filePathsCollection) + { + foreach (FilePath filePath in filePaths) + { + if (!results.TryGetValue(filePath.Name, out collection)) + { + results.Add(filePath.Name, []); + if (!results.TryGetValue(filePath.Name, out collection)) + throw new Exception(); + } + collection.Add(filePath.FullName); + } + } + return results; + } + internal static int LookForAbandoned(ReadOnlyCollection jsonFilesCollection, IReadOnlyDictionary> fileNamesToFiles, string extension) { string fileName; @@ -147,37 +175,37 @@ internal abstract partial class XDirectory return result; } - internal static List GetFiles(ReadOnlyCollection filesCollection, IReadOnlyDictionary> fileNamesToFiles, string extension, IReadOnlyDictionary> compareFileNamesToFiles) + internal static List GetFiles(Properties.IPropertyConfiguration propertyConfiguration, ReadOnlyCollection> filePathsCollection, IReadOnlyDictionary> fileNamesToFiles, string extension, IReadOnlyDictionary> compareFileNamesToFiles) { List results = []; string? match; - string fileName; bool uniqueFileName; List? collection; bool? isNotUniqueAndNeedsReview; - foreach (string[] files in filesCollection) + foreach (ReadOnlyCollection filePaths in filePathsCollection) { - foreach (string file in files) + foreach (FilePath filePath in filePaths) { isNotUniqueAndNeedsReview = null; - fileName = Path.GetFileName(file); - if (!fileNamesToFiles.TryGetValue(fileName, out collection)) + if (propertyConfiguration.IgnoreExtensions.Contains(filePath.ExtensionLowered)) + continue; + if (!fileNamesToFiles.TryGetValue(filePath.Name, out collection)) throw new Exception(); uniqueFileName = collection.Count == 1; if (!uniqueFileName) - isNotUniqueAndNeedsReview = GetIsNotUniqueAndNeedsReview(file, collection); - if (!compareFileNamesToFiles.TryGetValue(string.Concat(fileName, extension), out collection)) - results.Add(new(file, uniqueFileName, isNotUniqueAndNeedsReview, [], null)); + isNotUniqueAndNeedsReview = GetIsNotUniqueAndNeedsReview(filePath.FullName, collection); + if (!compareFileNamesToFiles.TryGetValue(string.Concat(filePath.Name, extension), out collection)) + results.Add(new(filePath.FullName, uniqueFileName, isNotUniqueAndNeedsReview, [], null)); else { if (collection.Count == 0) - results.Add(new(file, uniqueFileName, isNotUniqueAndNeedsReview, collection, null)); + results.Add(new(filePath.FullName, uniqueFileName, isNotUniqueAndNeedsReview, collection, null)); else if (uniqueFileName && collection.Count == 1) - results.Add(new(file, uniqueFileName, isNotUniqueAndNeedsReview, collection, collection.First())); + results.Add(new(filePath.FullName, uniqueFileName, isNotUniqueAndNeedsReview, collection, collection.First())); else { - match = GetMatch(file, collection); - results.Add(new(file, uniqueFileName, isNotUniqueAndNeedsReview, collection, match)); + match = GetMatch(filePath.FullName, collection); + results.Add(new(filePath.FullName, uniqueFileName, isNotUniqueAndNeedsReview, collection, match)); } } } @@ -260,29 +288,46 @@ internal abstract partial class XDirectory continue; checkFile = file.Replace(find, replace); if (File.Exists(checkFile)) + { + File.Delete(checkFile); continue; + } File.Move(file, checkFile); } } - private static FilePath[] GetSortedRecords(Properties.IPropertyConfiguration propertyConfiguration, ReadOnlyCollection filesCollection) + private static FilePath[] GetSortedRecords(ReadOnlyCollection> filePathsCollection) { List results = []; - FilePath filePath; - Models.FileHolder fileHolder; - foreach (string[] files in filesCollection) + foreach (ReadOnlyCollection filePaths in filePathsCollection) { - foreach (string file in files) - { - fileHolder = IFileHolder.Get(file); - filePath = FilePath.Get(propertyConfiguration, fileHolder, index: null); + foreach (FilePath filePath in filePaths) results.Add(filePath); - } } return (from l in results orderby l.CreationTicks, l.FullName.Length descending select l).ToArray(); } - internal static (string[], List<(FilePath, string)>) GetToDoCollection(Properties.IPropertyConfiguration propertyConfiguration, bool copyDuplicates, bool ifCanUseId, ReadOnlyCollection filesCollection, string[] directories, Action? tick) + internal static ReadOnlyCollection> GetFilePathCollections(Properties.IPropertyConfiguration propertyConfiguration, ReadOnlyCollection filesCollection) + { + List> results = []; + FilePath filePath; + List filePaths; + Models.FileHolder fileHolder; + foreach (string[] files in filesCollection) + { + filePaths = []; + foreach (string file in files) + { + fileHolder = IFileHolder.Get(file); + filePath = FilePath.Get(propertyConfiguration, fileHolder, index: null); + filePaths.Add(filePath); + } + results.Add(new(filePaths)); + } + return new(results); + } + + internal static (string[], List<(FilePath, string)>) GetToDoCollection(Properties.IPropertyConfiguration propertyConfiguration, bool copyDuplicates, bool ifCanUseId, ReadOnlyCollection> filePathsCollection, string[] directories, Action? tick) { List<(FilePath, string)> results = []; string paddedId; @@ -300,7 +345,8 @@ internal abstract partial class XDirectory List distinct = []; Models.FileHolder fileHolder; List distinctDirectories = []; - FilePath[] sortedRecords = GetSortedRecords(propertyConfiguration, filesCollection); + FilePath[] sortedRecords = GetSortedRecords(filePathsCollection); + bool isOffsetDeterministicHashCode = IId.IsOffsetDeterministicHashCode(propertyConfiguration); for (int i = 0; i < sortedRecords.Length; i++) { tick?.Invoke(); @@ -323,7 +369,7 @@ internal abstract partial class XDirectory } if (ifCanUseId && filePath.IsIntelligentIdFormat && filePath.Id is not null && filePath.DirectoryName is not null) { - paddedId = IId.GetPaddedId(propertyConfiguration, filePath.Id.Value, filePath.IsIgnore, i); + paddedId = IId.GetPaddedId(propertyConfiguration, filePath.Id.Value, filePath.HasIgnoreKeyword, filePath.HasDateTimeOriginal, i); paddedIdFile = Path.Combine(filePath.DirectoryName, $"{paddedId}{filePath.ExtensionLowered}"); if (!File.Exists(paddedIdFile)) { @@ -341,8 +387,22 @@ internal abstract partial class XDirectory { if (filePath.Id is null) throw new NullReferenceException(nameof(filePath.Id)); - intelligentId = IId.GetIntelligentId(propertyConfiguration, filePath.Id.Value, filePath.IsIgnore); - checkFile = Path.Combine(directory, $"{intelligentId}{filePath.ExtensionLowered}"); + intelligentId = IId.GetIntelligentId(propertyConfiguration, filePath.Id.Value, filePath.HasIgnoreKeyword, filePath.HasDateTimeOriginal); + if (!isOffsetDeterministicHashCode) + checkFile = Path.Combine(directory, $"{intelligentId}{filePath.ExtensionLowered}"); + else + { + if (filePath.DirectoryName is null) + continue; + paddedId = IId.GetPaddedId(propertyConfiguration, filePath.Id.Value, filePath.HasIgnoreKeyword, filePath.HasDateTimeOriginal, i); + paddedIdFile = Path.Combine(filePath.DirectoryName, $"{paddedId}{filePath.ExtensionLowered}"); + if (File.Exists(paddedIdFile)) + continue; + File.Move(filePath.FullName, paddedIdFile); + if (!paddedCheck) + paddedCheck = true; + continue; + } } if ((filePath.Id is not null && distinctIds.Contains(filePath.Id.Value)) || distinct.Contains(checkFile)) { @@ -380,6 +440,8 @@ internal abstract partial class XDirectory if (!distinctDirectories.Contains(directory)) distinctDirectories.Add(directory); } + if (!isOffsetDeterministicHashCode) + throw new Exception("Change Configuration Offset after creating iso file with images sorted!"); if (paddedCheck) throw new Exception("Maybe need to restart application!"); return (distinctDirectories.ToArray(), results);