mapped-ids-then-whole-percentages-to-location-container save-extracted-face save-extracted-java-script-object-notation
285 lines
15 KiB
C#
285 lines
15 KiB
C#
using Microsoft.Extensions.Logging;
|
|
using ShellProgressBar;
|
|
using System.Collections.ObjectModel;
|
|
using System.Drawing;
|
|
using View_by_Distance.Compare.Models;
|
|
using View_by_Distance.Distance.Models.Stateless;
|
|
using View_by_Distance.Face.Models.Stateless;
|
|
using View_by_Distance.Metadata.Models;
|
|
using View_by_Distance.People.Models.Stateless;
|
|
using View_by_Distance.Shared.Models;
|
|
using View_by_Distance.Shared.Models.Stateless;
|
|
|
|
namespace View_by_Distance.Compare;
|
|
|
|
public partial class Compare : ICompare, IDisposable
|
|
{
|
|
|
|
public long Ticks { get; init; }
|
|
public int? CurrentTick => _ProgressBar?.CurrentTick;
|
|
|
|
private ProgressBar? _ProgressBar;
|
|
private readonly ProgressBarOptions _ProgressBarOptions;
|
|
|
|
void ICompare.Tick() =>
|
|
_ProgressBar?.Tick();
|
|
|
|
void IDisposable.Dispose()
|
|
{
|
|
_ProgressBar?.Dispose();
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
void ICompare.ConstructProgressBar(int maxTicks, string message)
|
|
{
|
|
_ProgressBar?.Dispose();
|
|
_ProgressBar = new(maxTicks, message, _ProgressBarOptions);
|
|
}
|
|
|
|
public Compare(List<string> args, ILogger<Program>? logger, AppSettings appSettings, bool isSilent, IConsole console)
|
|
{
|
|
if (isSilent)
|
|
{ }
|
|
if (args is null)
|
|
throw new NullReferenceException(nameof(args));
|
|
if (console is null)
|
|
throw new NullReferenceException(nameof(console));
|
|
ICompare compare = this;
|
|
Ticks = DateTime.Now.Ticks;
|
|
_ProgressBarOptions = new() { ProgressCharacter = '─', ProgressBarOnBottom = true, DisableBottomPercentage = true };
|
|
CompareWork(logger, appSettings, compare);
|
|
}
|
|
|
|
private void CompareWork(ILogger<Program>? logger, AppSettings appSettings, ICompare compare)
|
|
{
|
|
const int updated = 0;
|
|
DistanceLimits? distanceLimits;
|
|
ReadOnlyCollection<LocationContainer> matrix;
|
|
logger?.LogInformation("{Ticks}", compare.Ticks);
|
|
ReadOnlyCollection<SaveContainer> saveContainers;
|
|
ReadOnlyCollection<ExifDirectory> exifDirectories;
|
|
ReadOnlyCollection<LocationContainer> preFiltered;
|
|
ReadOnlyCollection<LocationContainer> postFiltered;
|
|
ReadOnlyDictionary<string, LocationContainer> onlyOne;
|
|
bool runToDoCollectionFirst = GetRunToDoCollectionFirst(appSettings, compare);
|
|
ReadOnlyCollections readOnlyCollections = GetReadOnlyCollections(appSettings);
|
|
ReadOnlyDictionary<int, ReadOnlyDictionary<int, LocationContainer>> mappedIdsThenWholePercentagesToLocationContainer = GetMappedIdsThenWholePercentagesToLocationContainer(appSettings, compare, readOnlyCollections);
|
|
if (appSettings.CompareSettings.SaveExtractedFaces || appSettings.CompareSettings.SaveExtractedJavaScriptObjectNotation)
|
|
SaveExtracted(appSettings, mappedIdsThenWholePercentagesToLocationContainer);
|
|
foreach (string outputResolution in appSettings.CompareSettings.OutputResolutions)
|
|
{
|
|
if (runToDoCollectionFirst || outputResolution.Any(char.IsNumber))
|
|
continue;
|
|
logger?.LogInformation("{outputResolution}", outputResolution);
|
|
exifDirectories = IFace.GetExifDirectories(appSettings.ResultSettings, appSettings.MetadataSettings, appSettings.DistanceSettings, appSettings.CompareSettings, compare, outputResolution);
|
|
preFiltered = IDistance.GetPreFilterLocationContainer(appSettings.DistanceSettings, appSettings.CompareSettings, compare, readOnlyCollections, mappedIdsThenWholePercentagesToLocationContainer, exifDirectories);
|
|
if (preFiltered.Count == 0)
|
|
continue;
|
|
if (appSettings.CompareSettings.SaveExtractedFaces || appSettings.CompareSettings.SaveExtractedJavaScriptObjectNotation)
|
|
SaveExtracted(appSettings, preFiltered);
|
|
distanceLimits = new(appSettings.DistanceSettings);
|
|
postFiltered = IDistance.GetPostFilterLocationContainer(preFiltered, distanceLimits);
|
|
if (postFiltered.Count == 0)
|
|
continue;
|
|
matrix = IDistance.GetMatrixLocationContainers(appSettings.DistanceSettings, compare, mappedIdsThenWholePercentagesToLocationContainer, distanceLimits, postFiltered);
|
|
if (matrix.Count == 0)
|
|
continue;
|
|
onlyOne = IDistance.GetOnlyOne(appSettings.DistanceSettings, matrix);
|
|
if (onlyOne.Count == 0)
|
|
continue;
|
|
saveContainers = IDistance.GetSaveContainers(appSettings.ResultSettings, appSettings.DistanceSettings, appSettings.CompareSettings, compare, outputResolution, onlyOne);
|
|
if (saveContainers.Count == 0)
|
|
continue;
|
|
IDistance.SaveContainers(appSettings.DistanceSettings, appSettings.CompareSettings, compare, updated, saveContainers);
|
|
}
|
|
}
|
|
|
|
private static bool GetRunToDoCollectionFirst(AppSettings appSettings, ICompare compare)
|
|
{
|
|
bool result = appSettings.DistanceSettings.SaveSortingWithoutPerson;
|
|
if (!result)
|
|
result = !IId.IsOffsetDeterministicHashCode(appSettings.MetadataSettings);
|
|
if (!result)
|
|
{
|
|
string[] directories;
|
|
directories = Directory.GetDirectories(appSettings.ResultSettings.RootDirectory, "*", SearchOption.TopDirectoryOnly);
|
|
if (directories.Length == 0)
|
|
result = true;
|
|
else
|
|
{
|
|
string seasonDirectory;
|
|
DirectoryInfo directoryInfo;
|
|
DateTime dateTime = new(compare.Ticks);
|
|
string rootDirectory = appSettings.ResultSettings.RootDirectory;
|
|
(int season, string seasonName) = IDate.GetSeason(dateTime.DayOfYear);
|
|
string eDistanceContentDirectory = IResult.GetResultsDateGroupDirectory(appSettings.ResultSettings, nameof(E_Distance), appSettings.ResultSettings.ResultContent);
|
|
FileSystemInfo fileSystemInfo = new DirectoryInfo(eDistanceContentDirectory);
|
|
string[] checkDirectories =
|
|
[
|
|
Path.Combine(rootDirectory, "Ancestry"),
|
|
Path.Combine(rootDirectory, "Facebook"),
|
|
Path.Combine(rootDirectory, "LinkedIn")
|
|
];
|
|
foreach (string checkDirectory in checkDirectories)
|
|
{
|
|
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)
|
|
{
|
|
directoryInfo = new(directory);
|
|
if (directoryInfo.LastWriteTime > fileSystemInfo.LastWriteTime)
|
|
{
|
|
result = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (result)
|
|
result = true;
|
|
if (!result)
|
|
result = false;
|
|
return result;
|
|
}
|
|
|
|
private static ReadOnlyCollections GetReadOnlyCollections(AppSettings appSettings)
|
|
{
|
|
ReadOnlyCollections result;
|
|
ReadOnlyCollection<PersonContainer> personContainers = IPeople.GetPersonContainers(appSettings.ResultSettings, appSettings.MetadataSettings, appSettings.PeopleSettings, appSettings.CompareSettings);
|
|
ReadOnlyCollection<long> personKeys = IPeople.GetPersonKeys(personContainers);
|
|
result = IPeople.GetReadOnlyCollections(appSettings.ResultSettings, appSettings.PeopleSettings, appSettings.DistanceSettings, appSettings.CompareSettings, personContainers, personKeys);
|
|
return result;
|
|
}
|
|
|
|
private static ReadOnlyDictionary<int, ReadOnlyDictionary<int, LocationContainer>> GetMappedIdsThenWholePercentagesToLocationContainer(AppSettings appSettings, ICompare compare, ReadOnlyCollections readOnlyCollections)
|
|
{
|
|
|
|
ReadOnlyDictionary<int, ReadOnlyDictionary<int, LocationContainer>> results;
|
|
results = IDistance.GetMappedIdsThenWholePercentagesToLocationContainer(appSettings.ResultSettings,
|
|
appSettings.MetadataSettings,
|
|
appSettings.PeopleSettings,
|
|
appSettings.DistanceSettings,
|
|
appSettings.CompareSettings,
|
|
compare,
|
|
readOnlyCollections);
|
|
if (results.Count == 0 && !appSettings.DistanceSettings.SaveSortingWithoutPerson)
|
|
throw new NotSupportedException($"Switch {nameof(appSettings.DistanceSettings.SaveSortingWithoutPerson)}!");
|
|
return results;
|
|
}
|
|
|
|
private void SaveExtracted(AppSettings appSettings, ReadOnlyDictionary<int, ReadOnlyDictionary<int, LocationContainer>> mappedIdsThenWholePercentagesToLocationContainer)
|
|
{
|
|
ReadOnlyCollection<string>? paths;
|
|
ReadOnlyDictionary<string, ReadOnlyCollection<string>> rootDirectoryFileNameToPaths = GetRootDirectoryFileNameToPaths(appSettings.ResultSettings, appSettings.CompareSettings);
|
|
foreach (KeyValuePair<int, ReadOnlyDictionary<int, LocationContainer>> keyValuePair in mappedIdsThenWholePercentagesToLocationContainer)
|
|
{
|
|
foreach (KeyValuePair<int, LocationContainer> keyValue in keyValuePair.Value)
|
|
{
|
|
if (keyValue.Value.ExifDirectory is null || keyValue.Value.FaceFile?.Location is null)
|
|
continue;
|
|
if (rootDirectoryFileNameToPaths.TryGetValue(keyValue.Value.FilePath.FileNameFirstSegment, out paths))
|
|
{
|
|
if (appSettings.CompareSettings.SaveExtractedJavaScriptObjectNotation && keyValue.Value.PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName is not null)
|
|
SaveExtractedJavaScriptObjectNotation(keyValue.Key, keyValue.Value, paths);
|
|
if (appSettings.CompareSettings.SaveExtractedFaces)
|
|
SaveExtractedFace(keyValuePair.Key, keyValue.Key, keyValue.Value.ExifDirectory, keyValue.Value.FaceFile.Location, keyValue.Value.PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName, paths);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void SaveExtracted(AppSettings appSettings, ReadOnlyCollection<LocationContainer> preFiltered)
|
|
{
|
|
ReadOnlyCollection<string>? paths;
|
|
ReadOnlyDictionary<string, ReadOnlyCollection<string>> rootDirectoryFileNameToPaths = GetRootDirectoryFileNameToPaths(appSettings.ResultSettings, appSettings.CompareSettings);
|
|
foreach (LocationContainer locationContainer in preFiltered)
|
|
{
|
|
if (locationContainer?.FilePath?.Id is null || locationContainer?.WholePercentages is null || locationContainer?.ExifDirectory is null || locationContainer?.FaceFile?.Location is null)
|
|
continue;
|
|
if (rootDirectoryFileNameToPaths.TryGetValue(locationContainer.FilePath.FileNameFirstSegment, out paths))
|
|
{
|
|
if (appSettings.CompareSettings.SaveExtractedJavaScriptObjectNotation && locationContainer.PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName is not null)
|
|
SaveExtractedJavaScriptObjectNotation(locationContainer.WholePercentages.Value, locationContainer, paths);
|
|
if (appSettings.CompareSettings.SaveExtractedFaces)
|
|
SaveExtractedFace(locationContainer.FilePath.Id.Value, locationContainer.WholePercentages.Value, locationContainer.ExifDirectory, locationContainer.FaceFile.Location, locationContainer.PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName, paths);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static ReadOnlyDictionary<string, ReadOnlyCollection<string>> GetRootDirectoryFileNameToPaths(ResultSettings resultSettings, CompareSettings compareSettings)
|
|
{
|
|
Dictionary<string, ReadOnlyCollection<string>> results = [];
|
|
string key;
|
|
string extension;
|
|
List<string>? collection;
|
|
Dictionary<string, List<string>> keyValuePairs = [];
|
|
string[] files = !compareSettings.SaveExtractedFaces && !compareSettings.SaveExtractedJavaScriptObjectNotation ? [] : Directory.GetFiles(resultSettings.RootDirectory, "*", SearchOption.AllDirectories);
|
|
foreach (string file in files)
|
|
{
|
|
extension = Path.GetExtension(file);
|
|
if (resultSettings.IgnoreExtensions.Contains(extension))
|
|
continue;
|
|
key = Path.GetFileNameWithoutExtension(file);
|
|
if (!keyValuePairs.TryGetValue(key, out collection))
|
|
{
|
|
keyValuePairs.Add(key, []);
|
|
if (!keyValuePairs.TryGetValue(key, out collection))
|
|
throw new Exception();
|
|
}
|
|
collection.Add(file);
|
|
}
|
|
foreach (KeyValuePair<string, List<string>> keyValuePair in keyValuePairs)
|
|
results.Add(keyValuePair.Key, keyValuePair.Value.AsReadOnly());
|
|
return results.AsReadOnly();
|
|
}
|
|
|
|
private static void SaveExtractedJavaScriptObjectNotation(int wholePercentages, LocationContainer locationContainer, ReadOnlyCollection<string> paths)
|
|
{
|
|
string file;
|
|
string json;
|
|
foreach (string path in paths)
|
|
{
|
|
json = locationContainer.GetWithoutEncoding();
|
|
file = $"{path}-{wholePercentages}.json";
|
|
_ = IPath.WriteAllText(file, json, updateDateWhenMatches: false, compareBeforeWrite: true, updateToWhenMatches: null);
|
|
}
|
|
}
|
|
|
|
private static void SaveExtractedFace(int id, int wholePercentages, ExifDirectory _, Location location, PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName? personKeyFormattedAndKeyTicksAndDisplayDirectoryName, ReadOnlyCollection<string> paths)
|
|
{
|
|
int width;
|
|
int height;
|
|
string person;
|
|
foreach (string path in paths)
|
|
{
|
|
width = location.Right - location.Left;
|
|
height = location.Bottom - location.Top;
|
|
person = personKeyFormattedAndKeyTicksAndDisplayDirectoryName is null ? string.Empty : $"-{personKeyFormattedAndKeyTicksAndDisplayDirectoryName.DisplayDirectoryName}";
|
|
ExtractFace(file: path,
|
|
width: width,
|
|
height: height,
|
|
left: location.Left,
|
|
top: location.Top,
|
|
suffix: $"-{id}-{wholePercentages}{person}-face.jpg");
|
|
}
|
|
}
|
|
|
|
private static void ExtractFace(string file, float width, float height, double left, double top, string suffix)
|
|
{
|
|
RectangleF rectangle = new((float)left, (float)top, width, height);
|
|
using Bitmap source = new(file);
|
|
using Bitmap bitmap = new((int)width, (int)height);
|
|
using (Graphics graphics = Graphics.FromImage(bitmap))
|
|
graphics.DrawImage(source, new RectangleF(0, 0, width, height), rectangle, GraphicsUnit.Pixel);
|
|
bitmap.Save($"{file}{suffix}");
|
|
}
|
|
|
|
} |