736 lines
37 KiB
C#
736 lines
37 KiB
C#
using ShellProgressBar;
|
|
using System.Text.Json;
|
|
using View_by_Distance.FaceRecognitionDotNet;
|
|
using View_by_Distance.Map.Models;
|
|
using View_by_Distance.Shared.Models;
|
|
using View_by_Distance.Shared.Models.Properties;
|
|
|
|
namespace View_by_Distance.Distance.Models;
|
|
|
|
public class E_Distance : Shared.Models.Methods.IFaceDistance
|
|
{
|
|
|
|
private readonly List<string> _Moved;
|
|
private readonly List<double?> _Debug;
|
|
private readonly List<string> _Renamed;
|
|
private readonly Serilog.ILogger? _Log;
|
|
private readonly string _ResultAllInOne;
|
|
private readonly int _FaceDistancePermyriad;
|
|
private readonly bool _DistanceRenameToMatch;
|
|
private readonly double _FaceDistanceTolerance;
|
|
private readonly int _SortingDaysDeltaTolerance;
|
|
private readonly bool _DistanceMoveUnableToMatch;
|
|
private readonly List<string> _AllMappedFaceFiles;
|
|
private readonly int _DistancePixelDistanceTolerance;
|
|
private readonly List<string> _AllMappedFaceFileNames;
|
|
private readonly double _FaceDistanceMinimumConfidence;
|
|
private readonly List<string> _DuplicateMappedFaceFiles;
|
|
private readonly int _FaceDistanceAreaPermilleTolerance;
|
|
private readonly int _SortingMaximumPerFaceShouldBeHigh;
|
|
|
|
public E_Distance(bool distanceMoveUnableToMatch, int distancePixelDistanceTolerance, bool distanceRenameToMatch, int faceDistanceAreaPermilleTolerance, double faceDistanceMinimumConfidence, int faceDistancePermyriad, double faceDistanceTolerance, string resultAllInOne, int sortingDaysDeltaTolerance, int sortingMaximumPerFaceShouldBeHigh)
|
|
{
|
|
_Debug = new();
|
|
_Moved = new();
|
|
_Renamed = new();
|
|
_AllMappedFaceFiles = new();
|
|
_AllMappedFaceFileNames = new();
|
|
_ResultAllInOne = resultAllInOne;
|
|
_DuplicateMappedFaceFiles = new();
|
|
_Log = Serilog.Log.ForContext<E_Distance>();
|
|
_DistanceRenameToMatch = distanceRenameToMatch;
|
|
_FaceDistancePermyriad = faceDistancePermyriad;
|
|
_FaceDistanceTolerance = faceDistanceTolerance;
|
|
_DistanceMoveUnableToMatch = distanceMoveUnableToMatch;
|
|
_SortingDaysDeltaTolerance = sortingDaysDeltaTolerance;
|
|
_FaceDistanceMinimumConfidence = faceDistanceMinimumConfidence;
|
|
_DistancePixelDistanceTolerance = distancePixelDistanceTolerance;
|
|
_FaceDistanceAreaPermilleTolerance = faceDistanceAreaPermilleTolerance;
|
|
_SortingMaximumPerFaceShouldBeHigh = sortingMaximumPerFaceShouldBeHigh;
|
|
}
|
|
|
|
public static void SaveFaceDistances(Property.Models.Configuration configuration, SortingContainer[] sortingContainers)
|
|
{
|
|
string eDistanceContentCollectionDirectory = Property.Models.Stateless.IResult.GetResultsDateGroupDirectory(configuration, nameof(E_Distance), "([])");
|
|
if (!Directory.Exists(eDistanceContentCollectionDirectory))
|
|
_ = Directory.CreateDirectory(eDistanceContentCollectionDirectory);
|
|
#pragma warning disable
|
|
string[] results = (from l in sortingContainers select l.ToString()).ToArray();
|
|
#pragma warning restore
|
|
string eDistanceContentFileName = Path.Combine(eDistanceContentCollectionDirectory, $"{configuration.ResultAllInOne}.tvs");
|
|
File.WriteAllLines(eDistanceContentFileName, results);
|
|
}
|
|
|
|
private static List<Sorting> GetSortingCollection(MapLogic mapLogic, List<FaceDistance> faceDistanceEncodings, int faceDistanceContainersLength, int i, FaceDistance faceDistanceEncoding)
|
|
{
|
|
List<Sorting> results;
|
|
List<FaceDistance> faceDistanceLengths = FaceRecognition.FaceDistances(faceDistanceEncodings, faceDistanceEncoding);
|
|
if (faceDistanceLengths.Count != faceDistanceContainersLength)
|
|
throw new NotSupportedException();
|
|
results = mapLogic.GetSortingCollection(i, faceDistanceEncoding, faceDistanceLengths);
|
|
return results;
|
|
}
|
|
|
|
private List<SortingContainer> GetSortingContainers(Face face, FaceDistance faceDistanceEncoding, List<Sorting> sortingCollection, int? useFiltersCounter)
|
|
{
|
|
List<SortingContainer> results = new();
|
|
SortingContainer sortingContainer;
|
|
Sorting[] collection = Shared.Models.Stateless.Methods.ISorting.Sort(sortingCollection);
|
|
double faceDistancePermyriad;
|
|
double sortingDaysDeltaTolerance;
|
|
double faceDistanceMinimumConfidence;
|
|
double faceDistanceAreaPermilleTolerance;
|
|
if (useFiltersCounter is null)
|
|
{
|
|
sortingDaysDeltaTolerance = _SortingDaysDeltaTolerance;
|
|
faceDistancePermyriad = _FaceDistancePermyriad;
|
|
faceDistanceAreaPermilleTolerance = _FaceDistanceAreaPermilleTolerance;
|
|
faceDistanceMinimumConfidence = _FaceDistanceMinimumConfidence;
|
|
}
|
|
else if (useFiltersCounter.Value == 1)
|
|
{
|
|
sortingDaysDeltaTolerance = _SortingDaysDeltaTolerance * 1.5;
|
|
faceDistancePermyriad = _FaceDistancePermyriad;
|
|
faceDistanceAreaPermilleTolerance = _FaceDistanceAreaPermilleTolerance;
|
|
faceDistanceMinimumConfidence = _FaceDistanceMinimumConfidence;
|
|
}
|
|
else if (useFiltersCounter.Value == 2)
|
|
{
|
|
sortingDaysDeltaTolerance = _SortingDaysDeltaTolerance;
|
|
faceDistancePermyriad = _FaceDistancePermyriad * 1.5;
|
|
faceDistanceAreaPermilleTolerance = _FaceDistanceAreaPermilleTolerance;
|
|
faceDistanceMinimumConfidence = _FaceDistanceMinimumConfidence;
|
|
}
|
|
else if (useFiltersCounter.Value == 3)
|
|
{
|
|
sortingDaysDeltaTolerance = _SortingDaysDeltaTolerance;
|
|
faceDistancePermyriad = _FaceDistancePermyriad;
|
|
faceDistanceAreaPermilleTolerance = _FaceDistanceAreaPermilleTolerance;
|
|
faceDistanceMinimumConfidence = _FaceDistanceMinimumConfidence * .5;
|
|
}
|
|
else if (useFiltersCounter.Value == 4)
|
|
{
|
|
sortingDaysDeltaTolerance = _SortingDaysDeltaTolerance * 2;
|
|
faceDistancePermyriad = _FaceDistancePermyriad * 1.5;
|
|
faceDistanceAreaPermilleTolerance = _FaceDistanceAreaPermilleTolerance;
|
|
faceDistanceMinimumConfidence = _FaceDistanceMinimumConfidence * .5;
|
|
}
|
|
else if (useFiltersCounter.Value == 5)
|
|
{
|
|
sortingDaysDeltaTolerance = _SortingDaysDeltaTolerance * 1.5;
|
|
faceDistancePermyriad = _FaceDistancePermyriad * 2;
|
|
faceDistanceAreaPermilleTolerance = _FaceDistanceAreaPermilleTolerance;
|
|
faceDistanceMinimumConfidence = _FaceDistanceMinimumConfidence * .5;
|
|
}
|
|
else if (useFiltersCounter.Value == 6)
|
|
{
|
|
sortingDaysDeltaTolerance = _SortingDaysDeltaTolerance * 1.5;
|
|
faceDistancePermyriad = _FaceDistancePermyriad * 1.5;
|
|
faceDistanceAreaPermilleTolerance = _FaceDistanceAreaPermilleTolerance;
|
|
faceDistanceMinimumConfidence = _FaceDistanceMinimumConfidence * .25;
|
|
}
|
|
else if (useFiltersCounter.Value == 7)
|
|
{
|
|
sortingDaysDeltaTolerance = _SortingDaysDeltaTolerance * 2.5;
|
|
faceDistancePermyriad = _FaceDistancePermyriad * 2;
|
|
faceDistanceAreaPermilleTolerance = _FaceDistanceAreaPermilleTolerance;
|
|
faceDistanceMinimumConfidence = _FaceDistanceMinimumConfidence * .25;
|
|
}
|
|
else if (useFiltersCounter.Value == 8)
|
|
{
|
|
sortingDaysDeltaTolerance = _SortingDaysDeltaTolerance * 2;
|
|
faceDistancePermyriad = _FaceDistancePermyriad * 2.5;
|
|
faceDistanceAreaPermilleTolerance = _FaceDistanceAreaPermilleTolerance;
|
|
faceDistanceMinimumConfidence = _FaceDistanceMinimumConfidence * .25;
|
|
}
|
|
else if (useFiltersCounter.Value == 9)
|
|
{
|
|
sortingDaysDeltaTolerance = _SortingDaysDeltaTolerance * 2;
|
|
faceDistancePermyriad = _FaceDistancePermyriad * 2;
|
|
faceDistanceAreaPermilleTolerance = _FaceDistanceAreaPermilleTolerance;
|
|
faceDistanceMinimumConfidence = 0;
|
|
}
|
|
else
|
|
{
|
|
sortingDaysDeltaTolerance = int.MaxValue;
|
|
faceDistancePermyriad = int.MaxValue;
|
|
faceDistanceAreaPermilleTolerance = 0;
|
|
faceDistanceMinimumConfidence = 0;
|
|
}
|
|
foreach (Sorting sorting in collection)
|
|
{
|
|
if (face.Mapping is null || faceDistanceEncoding.NormalizedPixelPercentage is null)
|
|
throw new NotSupportedException();
|
|
if (sorting.DaysDelta > sortingDaysDeltaTolerance || sorting.DistancePermyriad > faceDistancePermyriad || face.Mapping.MappingFromLocation.Confidence < faceDistanceMinimumConfidence || face.Mapping.MappingFromLocation.AreaPermille < faceDistanceAreaPermilleTolerance)
|
|
continue;
|
|
sortingContainer = new(face.Mapping, sorting);
|
|
results.Add(sortingContainer);
|
|
if (results.Count >= _SortingMaximumPerFaceShouldBeHigh)
|
|
break;
|
|
}
|
|
return results;
|
|
}
|
|
|
|
private static FaceDistanceContainer[] GetOrderedFaceDistanceContainers(List<FaceDistanceContainer> collection)
|
|
{
|
|
FaceDistanceContainer[] results;
|
|
results = (from l in collection orderby l.FaceDistance.Encoding is not null select l).ToArray();
|
|
if (results.Any() && results[0].FaceDistance.Encoding is null)
|
|
throw new Exception("Sorting failed!");
|
|
return results;
|
|
}
|
|
|
|
private static FaceDistanceContainer[] GetOrderedFaceDistanceContainers(Face[] distinctFilteredFaces)
|
|
{
|
|
FaceDistanceContainer[] results;
|
|
FaceDistance faceDistance;
|
|
FaceDistanceContainer faceDistanceContainer;
|
|
List<FaceDistanceContainer> collection = new();
|
|
foreach (Face face in distinctFilteredFaces)
|
|
{
|
|
if (face.Mapping is null)
|
|
throw new NotSupportedException();
|
|
if (face.FaceDistance?.Encoding is not FaceRecognitionDotNet.FaceEncoding faceEncoding)
|
|
continue;
|
|
faceDistance = new(face.Mapping.MappingFromLocation.Confidence, faceEncoding, face.Mapping.MappingFromItem.Id, face.Mapping.MappingFromItem.IsWrongYear, face.Mapping.MappingFromItem.MinimumDateTime, face.Mapping.MappingFromLocation.NormalizedPixelPercentage);
|
|
faceDistanceContainer = new(face, faceDistance);
|
|
collection.Add(faceDistanceContainer);
|
|
}
|
|
results = GetOrderedFaceDistanceContainers(collection);
|
|
return results;
|
|
}
|
|
|
|
private static List<FaceDistance> GetFaceDistanceEncodings(FaceDistanceContainer[] faceDistanceContainers)
|
|
{
|
|
List<FaceDistance> faceDistanceEncodings = new();
|
|
foreach (FaceDistanceContainer faceDistanceContainer in faceDistanceContainers)
|
|
{
|
|
if (faceDistanceContainer.FaceDistance.Encoding is null)
|
|
continue;
|
|
faceDistanceEncodings.Add(faceDistanceContainer.FaceDistance);
|
|
}
|
|
return faceDistanceEncodings;
|
|
}
|
|
|
|
public SortingContainer[] SetFaceMappingSortingCollectionThenGetSortingContainers(int maxDegreeOfParallelism, long ticks, MapLogic mapLogic, Face[] distinctFilteredFaces, int? useFiltersCounter)
|
|
{
|
|
SortingContainer[] results;
|
|
List<SortingContainer> collection = new();
|
|
int totalSeconds = (int)Math.Floor(new TimeSpan(DateTime.Now.Ticks - ticks).TotalSeconds);
|
|
ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = maxDegreeOfParallelism };
|
|
FaceDistanceContainer[] faceDistanceContainers = GetOrderedFaceDistanceContainers(distinctFilteredFaces);
|
|
List<FaceDistance> faceDistanceEncodings = GetFaceDistanceEncodings(faceDistanceContainers);
|
|
string message = $") {faceDistanceContainers.Length:000} Get Sorting Containers Then Set Face Mapping Sorting Collection - {totalSeconds} total second(s)";
|
|
ProgressBarOptions options = new() { ProgressCharacter = '─', ProgressBarOnBottom = true, DisableBottomPercentage = true };
|
|
using ProgressBar progressBar = new(faceDistanceContainers.Length, message, options);
|
|
_ = Parallel.For(0, faceDistanceContainers.Length, parallelOptions, (i, state) =>
|
|
{
|
|
progressBar.Tick();
|
|
Face face = faceDistanceContainers[i].Face;
|
|
if (face.Mapping is null)
|
|
throw new NotSupportedException();
|
|
FaceDistance faceDistanceEncoding = faceDistanceContainers[i].FaceDistance;
|
|
if (mapLogic.Used(faceDistanceEncoding))
|
|
return;
|
|
List<Sorting> sortingCollection = GetSortingCollection(mapLogic, faceDistanceEncodings, faceDistanceContainers.Length, i, faceDistanceEncoding);
|
|
if (!sortingCollection.Any())
|
|
return;
|
|
List<SortingContainer> sortingContainers = GetSortingContainers(face, faceDistanceEncoding, sortingCollection, useFiltersCounter);
|
|
if (sortingContainers.Any())
|
|
{
|
|
lock (collection)
|
|
collection.AddRange(sortingContainers);
|
|
}
|
|
});
|
|
if (!collection.Any())
|
|
results = Array.Empty<SortingContainer>();
|
|
else
|
|
results = Shared.Models.Stateless.Methods.ISortingContainer.Sort(collection);
|
|
return results;
|
|
}
|
|
|
|
public static Mapping[] GetSelectedMappingCollection(Face[] distinctFilteredFaces)
|
|
{
|
|
Mapping[] results;
|
|
IEnumerable<Mapping> collection = from l in distinctFilteredFaces orderby l.Mapping?.MappingFromItem.MinimumDateTime descending select l.Mapping;
|
|
results = (from l in collection where l is not null select l).ToArray();
|
|
return results;
|
|
}
|
|
|
|
public static void SetFaceDistances(int maxDegreeOfParallelism, long ticks, Face[] distinctFilteredFaces)
|
|
{
|
|
int totalSeconds = (int)Math.Floor(new TimeSpan(DateTime.Now.Ticks - ticks).TotalSeconds);
|
|
ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = maxDegreeOfParallelism };
|
|
string message = $") {distinctFilteredFaces.Length:000} Load Face Encoding - {totalSeconds} total second(s)";
|
|
ProgressBarOptions options = new() { ProgressCharacter = '─', ProgressBarOnBottom = true, DisableBottomPercentage = true };
|
|
using ProgressBar progressBar = new(distinctFilteredFaces.Length, message, options);
|
|
_ = Parallel.For(0, distinctFilteredFaces.Length, parallelOptions, (i, state) =>
|
|
{
|
|
Face face = distinctFilteredFaces[i];
|
|
if (face.FaceEncoding is null || face.Mapping is null)
|
|
throw new NotSupportedException();
|
|
if (face.FaceDistance?.Encoding is not null && face.FaceDistance.Encoding is FaceRecognitionDotNet.FaceEncoding faceEncoding)
|
|
return;
|
|
progressBar.Tick();
|
|
faceEncoding = FaceRecognition.LoadFaceEncoding(face.FaceEncoding.RawEncoding);
|
|
FaceDistance faceDistance = new(face.Mapping.MappingFromLocation.Confidence, faceEncoding, face.Mapping.MappingFromItem.Id, face.Mapping.MappingFromItem.IsWrongYear, face.Mapping.MappingFromItem.MinimumDateTime, face.Mapping.MappingFromLocation.NormalizedPixelPercentage);
|
|
lock (face)
|
|
face.SetFaceDistance(faceDistance);
|
|
});
|
|
}
|
|
|
|
void Shared.Models.Methods.IFaceDistance.SavePossiblyNewPersonContainers(IPropertyConfiguration propertyConfiguration, string personBirthdayFormat, string facesFileNameExtension, string? a2PeopleSingletonDirectory, Dictionary<long, PersonContainer> personKeyToPersonContainer, List<(string[], PersonContainer)> possiblyNewPersonDisplayDirectoryNamesAndPersonContainer)
|
|
{
|
|
char @char;
|
|
string json;
|
|
string[] files;
|
|
string checkFile;
|
|
string[] segments;
|
|
const int zero = 0;
|
|
string personKeyFormatted;
|
|
string personDisplayDirectory;
|
|
PersonBirthday personBirthday;
|
|
string personDisplayDirectoryName;
|
|
string checkPersonDisplayDirectory;
|
|
string checkPersonKeyFormattedDirectory;
|
|
char[] chars = Shared.Models.Stateless.Methods.IAge.GetChars();
|
|
JsonSerializerOptions jsonSerializerOptions = new() { WriteIndented = true };
|
|
foreach ((string[] personDisplayDirectoryNames, PersonContainer personContainer) in possiblyNewPersonDisplayDirectoryNamesAndPersonContainer)
|
|
{
|
|
if (a2PeopleSingletonDirectory is null || personContainer.Key is null || personContainer.Birthdays is null || !personContainer.Birthdays.Any())
|
|
continue;
|
|
personBirthday = personContainer.Birthdays[zero];
|
|
personDisplayDirectoryName = personDisplayDirectoryNames[^1];
|
|
personDisplayDirectory = Path.Combine(personDisplayDirectoryNames);
|
|
personKeyFormatted = Shared.Models.Stateless.Methods.IPersonBirthday.GetFormatted(personBirthdayFormat, personBirthday);
|
|
segments = personDisplayDirectoryName.Split(chars);
|
|
if (segments.Length != 2)
|
|
@char = '_';
|
|
else
|
|
@char = personDisplayDirectoryName[segments[zero].Length];
|
|
checkPersonDisplayDirectory = Path.Combine(a2PeopleSingletonDirectory, @char.ToString(), personDisplayDirectoryName);
|
|
checkPersonKeyFormattedDirectory = Path.Combine(checkPersonDisplayDirectory, personKeyFormatted);
|
|
if (Directory.Exists(checkPersonKeyFormattedDirectory))
|
|
continue;
|
|
_ = Directory.CreateDirectory(checkPersonKeyFormattedDirectory);
|
|
checkFile = Path.Combine(checkPersonKeyFormattedDirectory, $"{personKeyFormatted}.json");
|
|
json = JsonSerializer.Serialize(personContainer.Person, jsonSerializerOptions);
|
|
_ = Shared.Models.Stateless.Methods.IPath.WriteAllText(checkFile, json, updateDateWhenMatches: false, compareBeforeWrite: true);
|
|
if (!Directory.Exists(personDisplayDirectory))
|
|
continue;
|
|
files = Directory.GetFiles(personDisplayDirectory, $"*{facesFileNameExtension}", SearchOption.TopDirectoryOnly);
|
|
foreach (string file in files)
|
|
{
|
|
checkFile = Path.Combine(checkPersonDisplayDirectory, Path.GetFileName(file));
|
|
if (File.Exists(checkFile))
|
|
continue;
|
|
File.Copy(files[0], checkFile);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void MoveUnableToMatch(string eDistanceContentDirectory, string mappedFaceFile, string mappedFaceFileName)
|
|
{
|
|
bool check;
|
|
string? directoryName = Path.GetDirectoryName(mappedFaceFile);
|
|
if (mappedFaceFileName is null || directoryName is null)
|
|
check = false;
|
|
else
|
|
{
|
|
if (string.IsNullOrEmpty(directoryName) || string.IsNullOrEmpty(directoryName) || !directoryName.Contains(eDistanceContentDirectory))
|
|
check = false;
|
|
else
|
|
{
|
|
List<string> directoryNames = new();
|
|
string? checkDirectoryName = directoryName;
|
|
for (int i = 0; i < int.MaxValue; i++)
|
|
{
|
|
if (string.IsNullOrEmpty(checkDirectoryName))
|
|
continue;
|
|
directoryNames.Add(Path.GetFileName(checkDirectoryName));
|
|
checkDirectoryName = Path.GetDirectoryName(checkDirectoryName);
|
|
if (string.IsNullOrEmpty(checkDirectoryName))
|
|
continue;
|
|
if (checkDirectoryName == eDistanceContentDirectory)
|
|
break;
|
|
}
|
|
if (string.IsNullOrEmpty(checkDirectoryName) || !directoryNames.Any() || !long.TryParse(directoryNames[^1][1..^1], out long directoryTicks))
|
|
{
|
|
check = false;
|
|
File.Delete(mappedFaceFile);
|
|
}
|
|
else
|
|
{
|
|
checkDirectoryName = Path.Combine(checkDirectoryName, $"({directoryTicks}{_FaceDistanceTolerance.ToString()[1..]})");
|
|
for (int i = directoryNames.Count - 1 - 1; i > -1; i--)
|
|
checkDirectoryName = Path.Combine(checkDirectoryName, directoryNames[i]);
|
|
if (!Directory.Exists(checkDirectoryName))
|
|
_ = Directory.CreateDirectory(checkDirectoryName);
|
|
File.Move(mappedFaceFile, Path.Combine(checkDirectoryName, mappedFaceFileName));
|
|
check = true;
|
|
}
|
|
}
|
|
}
|
|
if (check)
|
|
_Moved.Add(mappedFaceFile);
|
|
}
|
|
|
|
public static string? GetFaceEncoding(string file)
|
|
{
|
|
string? result;
|
|
List<string> results = new();
|
|
const string comment = "Comment: ";
|
|
if (File.Exists(file))
|
|
{
|
|
IReadOnlyList<MetadataExtractor.Directory> directories = MetadataExtractor.ImageMetadataReader.ReadMetadata(file);
|
|
foreach (MetadataExtractor.Directory directory in directories)
|
|
{
|
|
if (directory.Name != "PNG-tEXt")
|
|
continue;
|
|
foreach (MetadataExtractor.Tag tag in directory.Tags)
|
|
{
|
|
if (tag.Name != "Textual Data" || string.IsNullOrEmpty(tag.Description))
|
|
continue;
|
|
if (!tag.Description.StartsWith(comment))
|
|
continue;
|
|
results.Add(tag.Description);
|
|
}
|
|
}
|
|
}
|
|
result = results.Any() ? results[0][comment.Length..] : null;
|
|
return result;
|
|
}
|
|
|
|
public static string? GetFaceLocation(string file)
|
|
{
|
|
string? result;
|
|
List<string> results = new();
|
|
const string artist = "Artist: ";
|
|
if (File.Exists(file))
|
|
{
|
|
IReadOnlyList<MetadataExtractor.Directory> directories = MetadataExtractor.ImageMetadataReader.ReadMetadata(file);
|
|
foreach (MetadataExtractor.Directory directory in directories)
|
|
{
|
|
if (directory.Name != "PNG-tEXt")
|
|
continue;
|
|
foreach (MetadataExtractor.Tag tag in directory.Tags)
|
|
{
|
|
if (tag.Name != "Textual Data" || string.IsNullOrEmpty(tag.Description))
|
|
continue;
|
|
if (!tag.Description.StartsWith(artist))
|
|
continue;
|
|
results.Add(tag.Description);
|
|
}
|
|
}
|
|
}
|
|
result = results.Any() ? results[0][artist.Length..] : null;
|
|
return result;
|
|
}
|
|
|
|
private static FaceDistanceContainer[] GetOrderedFaceDistanceContainers(MappingFromItem mappingFromItem, List<Face> faces)
|
|
{
|
|
FaceDistanceContainer[] results;
|
|
FaceDistance faceDistance;
|
|
int normalizedPixelPercentage;
|
|
FaceDistanceContainer faceDistanceContainer;
|
|
List<FaceDistanceContainer> collection = new();
|
|
foreach (Face face in faces)
|
|
{
|
|
if (face.FaceEncoding is null || face.Location is null || face.OutputResolution is null)
|
|
throw new NotSupportedException();
|
|
normalizedPixelPercentage = Shared.Models.Stateless.Methods.ILocation.GetNormalizedPixelPercentage(face.Location, Shared.Models.Stateless.ILocation.Digits, Shared.Models.Stateless.ILocation.Factor, face.OutputResolution);
|
|
if (face.FaceDistance?.Encoding is not null && face.FaceDistance.Encoding is FaceRecognitionDotNet.FaceEncoding faceEncoding)
|
|
faceDistance = new(face.Location.Confidence, faceEncoding, mappingFromItem.Id, mappingFromItem.IsWrongYear, mappingFromItem.MinimumDateTime, normalizedPixelPercentage);
|
|
else
|
|
{
|
|
faceEncoding = FaceRecognition.LoadFaceEncoding(face.FaceEncoding.RawEncoding);
|
|
faceDistance = new(face.Location.Confidence, faceEncoding, mappingFromItem.Id, mappingFromItem.IsWrongYear, mappingFromItem.MinimumDateTime, normalizedPixelPercentage);
|
|
lock (faces)
|
|
face.SetFaceDistance(faceDistance);
|
|
}
|
|
faceDistanceContainer = new(face, faceDistance);
|
|
collection.Add(faceDistanceContainer);
|
|
}
|
|
results = GetOrderedFaceDistanceContainers(collection);
|
|
return results;
|
|
}
|
|
|
|
private static List<(Face Face, double? Length)> GetValues(MappingFromItem mappingFromItem, List<Face> faces, string json)
|
|
{
|
|
List<(Face Face, double? Length)> results = new();
|
|
Face face;
|
|
FaceDistance faceDistanceLength;
|
|
Shared.Models.FaceEncoding? modelsFaceEncoding = JsonSerializer.Deserialize<Shared.Models.FaceEncoding>(json);
|
|
if (modelsFaceEncoding is null)
|
|
throw new NotSupportedException();
|
|
FaceRecognitionDotNet.FaceEncoding faceRecognitionDotNetFaceEncoding = FaceRecognition.LoadFaceEncoding(modelsFaceEncoding.RawEncoding);
|
|
FaceDistance faceDistanceEncoding = new(faceRecognitionDotNetFaceEncoding);
|
|
FaceDistanceContainer[] faceDistanceContainers = GetOrderedFaceDistanceContainers(mappingFromItem, faces);
|
|
int faceDistanceContainersLength = faceDistanceContainers.Length;
|
|
if (faceDistanceContainersLength != faces.Count)
|
|
throw new NotSupportedException();
|
|
List<FaceDistance> faceDistanceEncodings = GetFaceDistanceEncodings(faceDistanceContainers);
|
|
if (faceDistanceEncodings.Count != faces.Count)
|
|
throw new NotSupportedException();
|
|
List<FaceDistance> faceDistanceLengths = FaceRecognition.FaceDistances(faceDistanceEncodings, faceDistanceEncoding);
|
|
if (faceDistanceLengths.Count != faceDistanceContainersLength)
|
|
throw new NotSupportedException();
|
|
for (int i = 0; i < faces.Count; i++)
|
|
{
|
|
face = faces[i];
|
|
faceDistanceLength = faceDistanceLengths[i];
|
|
if (faceDistanceLength.Length is null)
|
|
throw new NotSupportedException();
|
|
results.Add(new(face, faceDistanceLength.Length.Value));
|
|
}
|
|
return results;
|
|
}
|
|
|
|
private (Face, double?)[] GetClosestFaceByDistanceIgnoringTolerance(MappingFromItem mappingFromItem, List<Face> faces, string json)
|
|
{
|
|
(Face, double?)[] results;
|
|
List<(Face Face, double? Length)> collection = GetValues(mappingFromItem, faces, json);
|
|
results = (from l in collection orderby l.Length select l).Take(1).ToArray();
|
|
(Face face, double? length) = results.First();
|
|
lock (_Debug)
|
|
_Debug.Add(length);
|
|
return results;
|
|
}
|
|
|
|
static (int?, int?) GetXY(int normalizedPixelPercentage, OutputResolution? outputResolution)
|
|
{
|
|
int? x;
|
|
int? y;
|
|
if (outputResolution is null)
|
|
{
|
|
x = null;
|
|
y = null;
|
|
}
|
|
else
|
|
{
|
|
string normalizedPixelPercentagePadded = Shared.Models.Stateless.Methods.ILocation.GetLeftPadded(Shared.Models.Stateless.ILocation.Digits, normalizedPixelPercentage);
|
|
(x, y) = Shared.Models.Stateless.Methods.ILocation.GetXY(Shared.Models.Stateless.ILocation.Digits, Shared.Models.Stateless.ILocation.Factor, outputResolution.Width, outputResolution.Height, normalizedPixelPercentagePadded);
|
|
}
|
|
return new(x, y);
|
|
}
|
|
|
|
private (Face, double?)[] GetClosestFaceByPixel(List<Face> faces, int? x1, int? y1)
|
|
{
|
|
(Face, double?)[] results;
|
|
int? x2;
|
|
int? y2;
|
|
double distance;
|
|
int normalizedPixelPercentageLoop;
|
|
string normalizedPixelPercentagePadded;
|
|
List<(Face Face, double? Order)> collection = new();
|
|
if (x1 is null || y1 is null)
|
|
throw new NotSupportedException();
|
|
foreach (Face face in faces)
|
|
{
|
|
if (face.Location is null || face.OutputResolution is null)
|
|
throw new NotSupportedException();
|
|
normalizedPixelPercentageLoop = Shared.Models.Stateless.Methods.ILocation.GetNormalizedPixelPercentage(face.Location, Shared.Models.Stateless.ILocation.Digits, Shared.Models.Stateless.ILocation.Factor, face.OutputResolution);
|
|
normalizedPixelPercentagePadded = Shared.Models.Stateless.Methods.ILocation.GetLeftPadded(Shared.Models.Stateless.ILocation.Digits, normalizedPixelPercentageLoop);
|
|
(x2, y2) = Shared.Models.Stateless.Methods.ILocation.GetXY(Shared.Models.Stateless.ILocation.Digits, Shared.Models.Stateless.ILocation.Factor, face.OutputResolution.Width, face.OutputResolution.Height, normalizedPixelPercentagePadded);
|
|
if (x2 is null || y2 is null)
|
|
throw new NotSupportedException();
|
|
distance = Math.Sqrt(Math.Pow(x1.Value - x2.Value, 2) + Math.Pow(y1.Value - y2.Value, 2));
|
|
collection.Add(new(face, distance));
|
|
}
|
|
results = (from l in collection orderby l.Order where l.Order < _DistancePixelDistanceTolerance select l).Take(1).ToArray();
|
|
return results;
|
|
}
|
|
|
|
private (Face, double?)[] GetClosestFaceByPixel(List<Face> faces, int normalizedPixelPercentage)
|
|
{
|
|
if (!faces.Any())
|
|
throw new NotSupportedException();
|
|
(Face, double?)[] results;
|
|
const int zero = 0;
|
|
OutputResolution? outputResolution = faces[zero].OutputResolution;
|
|
(int? x1, int? y1) = GetXY(normalizedPixelPercentage, outputResolution);
|
|
results = GetClosestFaceByPixel(faces, x1, y1);
|
|
return results;
|
|
}
|
|
|
|
private (Face, double?)[] GetClosestFaceByPixel(List<Face> faces, string json)
|
|
{
|
|
if (!faces.Any())
|
|
throw new NotSupportedException();
|
|
(Face, double?)[] results;
|
|
const int zero = 0;
|
|
OutputResolution? outputResolution = faces[zero].OutputResolution;
|
|
if (outputResolution is null)
|
|
throw new NullReferenceException(nameof(outputResolution));
|
|
Location? location = JsonSerializer.Deserialize<Location>(json);
|
|
if (location is null)
|
|
throw new NullReferenceException(nameof(location));
|
|
int normalizedPixelPercentage = Shared.Models.Stateless.Methods.ILocation.GetNormalizedPixelPercentage(location, Shared.Models.Stateless.ILocation.Digits, Shared.Models.Stateless.ILocation.Factor, outputResolution);
|
|
(int? x1, int? y1) = GetXY(normalizedPixelPercentage, outputResolution);
|
|
results = GetClosestFaceByPixel(faces, x1, y1);
|
|
return results;
|
|
}
|
|
|
|
private static List<(Face, double?)> GetMatchingFacesByFaceEncoding(List<Face> faces, string? json)
|
|
{
|
|
List<(Face, double?)> results = new();
|
|
string check;
|
|
foreach (Face face in faces)
|
|
{
|
|
if (json is null || face.FaceEncoding is null)
|
|
continue;
|
|
if (!json.Contains(face.FaceEncoding.RawEncoding[0].ToString()))
|
|
continue;
|
|
check = JsonSerializer.Serialize(face.FaceEncoding);
|
|
if (check != json)
|
|
continue;
|
|
results.Add(new(face, 0));
|
|
}
|
|
return results;
|
|
}
|
|
|
|
private static FileInfo? CheckFileThenGetFileInfo(string facesFileNameExtension, MappingFromItem mappingFromItem, string mappedFaceFile, List<(Face, double?)> checkFaces)
|
|
{
|
|
FileInfo? result = null;
|
|
string checkFile;
|
|
string? mappedFaceDirectory;
|
|
string deterministicHashCodeKey;
|
|
foreach ((Face face, _) in checkFaces)
|
|
{
|
|
if (checkFaces.Count != 1)
|
|
break;
|
|
if (face.FaceEncoding is null || face.Location is null || face.OutputResolution is null)
|
|
throw new NotSupportedException();
|
|
mappedFaceDirectory = Path.GetDirectoryName(mappedFaceFile);
|
|
if (mappedFaceDirectory is null)
|
|
throw new NotSupportedException();
|
|
deterministicHashCodeKey = Shared.Models.Stateless.Methods.IMapping.GetDeterministicHashCodeKey(mappingFromItem.Id, face.Location, Shared.Models.Stateless.ILocation.Digits, Shared.Models.Stateless.ILocation.Factor, face.OutputResolution);
|
|
checkFile = Path.Combine(mappedFaceDirectory, $"{deterministicHashCodeKey}{mappingFromItem.ImageFileHolder.ExtensionLowered}{facesFileNameExtension}");
|
|
if (checkFile == mappedFaceFile)
|
|
continue;
|
|
result = new FileInfo(checkFile);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private void AppendMatchingDuplicates(string mappedFaceFile, string[] matches)
|
|
{
|
|
string checkFile;
|
|
FileInfo fileInfo = new(mappedFaceFile);
|
|
List<(long Length, string FullName)> collection = new();
|
|
if (fileInfo.Exists)
|
|
collection.Add(new(fileInfo.Length, fileInfo.FullName));
|
|
lock (_DuplicateMappedFaceFiles)
|
|
_DuplicateMappedFaceFiles.Add(mappedFaceFile);
|
|
foreach (string match in matches)
|
|
{
|
|
fileInfo = new(match);
|
|
if (!fileInfo.Exists)
|
|
continue;
|
|
collection.Add(new(fileInfo.Length, fileInfo.FullName));
|
|
break;
|
|
}
|
|
collection = collection.OrderBy(l => l.Length).ToList();
|
|
for (int i = 0; i < collection.Count - 1; i++)
|
|
{
|
|
checkFile = string.Concat(collection[i].FullName, ".dup");
|
|
if (File.Exists(checkFile))
|
|
continue;
|
|
File.Move(collection[i].FullName, checkFile);
|
|
}
|
|
}
|
|
|
|
public void LookForMatchFacesAndPossiblyRename(string facesFileNameExtension, string? eDistanceContentDirectory, MappingFromItem mappingFromItem, List<Face> faces, List<(string MappedFaceFile, int normalizedPixelPercentage)> collection)
|
|
{
|
|
string? json;
|
|
string[] matches;
|
|
FileInfo? fileInfo;
|
|
string mappedFaceFileName;
|
|
List<(Face, double?)> checkFaces = new();
|
|
foreach ((string mappedFaceFile, int normalizedPixelPercentage) in collection)
|
|
{
|
|
mappedFaceFileName = Path.GetFileName(mappedFaceFile);
|
|
if (_DuplicateMappedFaceFiles.Contains(mappedFaceFileName))
|
|
continue;
|
|
checkFaces.Clear();
|
|
json = GetFaceEncoding(mappedFaceFile);
|
|
if (json is null)
|
|
{
|
|
if (!string.IsNullOrEmpty(eDistanceContentDirectory) && _DistanceMoveUnableToMatch)
|
|
MoveUnableToMatch(eDistanceContentDirectory, mappedFaceFile, mappedFaceFileName);
|
|
continue;
|
|
}
|
|
checkFaces.AddRange(GetMatchingFacesByFaceEncoding(faces, json));
|
|
if (checkFaces.Count == 1)
|
|
_Debug.Add(0);
|
|
if (checkFaces.Count != 1 && !string.IsNullOrEmpty(json))
|
|
{
|
|
checkFaces.Clear();
|
|
if (json is null)
|
|
throw new NotSupportedException();
|
|
checkFaces.AddRange(GetClosestFaceByDistanceIgnoringTolerance(mappingFromItem, faces, json));
|
|
}
|
|
if (checkFaces.Count != 1 && _DistancePixelDistanceTolerance > 0)
|
|
{
|
|
checkFaces.Clear();
|
|
json = GetFaceLocation(mappedFaceFile);
|
|
if (json is not null)
|
|
checkFaces.AddRange(GetClosestFaceByPixel(faces, json));
|
|
else
|
|
checkFaces.AddRange(GetClosestFaceByPixel(faces, normalizedPixelPercentage));
|
|
throw new NotImplementedException("Without a tolerance this should not ever occur!");
|
|
}
|
|
if (!checkFaces.Any())
|
|
{
|
|
if (!string.IsNullOrEmpty(eDistanceContentDirectory) && _DistanceMoveUnableToMatch)
|
|
MoveUnableToMatch(eDistanceContentDirectory, mappedFaceFile, mappedFaceFileName);
|
|
continue;
|
|
}
|
|
if (checkFaces.Count != 1)
|
|
{
|
|
if (!string.IsNullOrEmpty(eDistanceContentDirectory) && _DistanceMoveUnableToMatch)
|
|
MoveUnableToMatch(eDistanceContentDirectory, mappedFaceFile, mappedFaceFileName);
|
|
continue;
|
|
}
|
|
fileInfo = CheckFileThenGetFileInfo(facesFileNameExtension, mappingFromItem, mappedFaceFile, checkFaces);
|
|
if (fileInfo is not null)
|
|
{
|
|
if (_DistanceRenameToMatch && fileInfo is not null)
|
|
{
|
|
if (fileInfo.Exists)
|
|
File.Delete(fileInfo.FullName);
|
|
File.Move(mappedFaceFile, fileInfo.FullName);
|
|
_Renamed.Add(mappedFaceFile);
|
|
}
|
|
continue;
|
|
}
|
|
if (_AllMappedFaceFileNames.Contains(mappedFaceFileName))
|
|
{
|
|
lock (_AllMappedFaceFiles)
|
|
matches = (from l in _AllMappedFaceFiles where l != mappedFaceFile && Path.GetFileName(l) == mappedFaceFileName select l).ToArray();
|
|
if (matches.Any())
|
|
AppendMatchingDuplicates(mappedFaceFile, matches);
|
|
}
|
|
lock (_AllMappedFaceFiles)
|
|
_AllMappedFaceFiles.Add(mappedFaceFile);
|
|
lock (_AllMappedFaceFileNames)
|
|
_AllMappedFaceFileNames.Add(mappedFaceFileName);
|
|
}
|
|
}
|
|
|
|
public void Clear()
|
|
{
|
|
double?[] debug = (from l in _Debug where l is null or not 0 select l).ToArray();
|
|
string debugMessage = $"{_Debug.Count - debug.Length} - {debug.Min()} - {_Debug.Max()}";
|
|
if (_Moved.Any() || _Renamed.Any() || _DuplicateMappedFaceFiles.Any())
|
|
throw new NotImplementedException("Restart!");
|
|
_Debug.Clear();
|
|
_Moved.Clear();
|
|
_Renamed.Clear();
|
|
_AllMappedFaceFiles.Clear();
|
|
_AllMappedFaceFileNames.Clear();
|
|
_DuplicateMappedFaceFiles.Clear();
|
|
}
|
|
|
|
} |