using System.Diagnostics; using System.Drawing; using System.Drawing.Imaging; using System.Reflection; using System.Text.Json; using System.Text.Json.Serialization; using View_by_Distance.Face.Models.Stateless; using View_by_Distance.FaceRecognitionDotNet; using View_by_Distance.Metadata.Models; 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; namespace View_by_Distance.Face.Models; /// // List /// public class D_Face { protected readonly string _FileNameExtension; public string FileNameExtension => _FileNameExtension; protected readonly string _HiddenFileNameExtension; public string HiddenFileNameExtension => _HiddenFileNameExtension; private readonly Model _Model; private readonly string _ArgZero; private readonly Serilog.ILogger? _Log; private readonly bool _OverrideForFaceImages; private readonly bool _LoadPhotoPrismLocations; private readonly ImageCodecInfo _ImageCodecInfo; private readonly ModelParameter _ModelParameter; private readonly PredictorModel _PredictorModel; private readonly bool _CheckDFaceAndUpWriteDates; private readonly bool _PropertiesChangedForFaces; private readonly ConstructorInfo _ConstructorInfo; private readonly float _RectangleIntersectMinimum; private readonly int _FaceDistanceHiddenImageFactor; private readonly EncoderParameters _EncoderParameters; private readonly ImageCodecInfo _HiddenImageCodecInfo; private readonly Dictionary _FileGroups; private readonly bool _ForceFaceLastWriteTimeToCreationTime; private readonly EncoderParameters _HiddenEncoderParameters; private readonly IPropertyConfiguration _PropertyConfiguration; private readonly JsonSerializerOptions _WriteIndentedAndWhenWritingNull; public D_Face( string argZero, IPropertyConfiguration propertyConfiguration, bool checkDFaceAndUpWriteDates, EncoderParameters encoderParameters, int faceDistanceHiddenImageFactor, string filenameExtension, bool forceFaceLastWriteTimeToCreationTime, EncoderParameters hiddenEncoderParameters, string hiddenFileNameExtension, ImageCodecInfo hiddenImageCodecInfo, ImageCodecInfo imageCodecInfo, bool loadPhotoPrismLocations, string modelDirectory, string modelName, bool overrideForFaceImages, string predictorModelName, bool propertiesChangedForFaces, float[] rectangleIntersectMinimums) { _ArgZero = argZero; _FileGroups = new(); _ImageCodecInfo = imageCodecInfo; _EncoderParameters = encoderParameters; _FileNameExtension = filenameExtension; _Log = Serilog.Log.ForContext(); _HiddenImageCodecInfo = hiddenImageCodecInfo; _OverrideForFaceImages = overrideForFaceImages; _PropertyConfiguration = propertyConfiguration; _HiddenEncoderParameters = hiddenEncoderParameters; _HiddenFileNameExtension = hiddenFileNameExtension; _LoadPhotoPrismLocations = loadPhotoPrismLocations; _CheckDFaceAndUpWriteDates = checkDFaceAndUpWriteDates; _PropertiesChangedForFaces = propertiesChangedForFaces; _RectangleIntersectMinimum = rectangleIntersectMinimums.Min(); _FaceDistanceHiddenImageFactor = faceDistanceHiddenImageFactor; _ForceFaceLastWriteTimeToCreationTime = forceFaceLastWriteTimeToCreationTime; (Model model, PredictorModel predictorModel, ModelParameter modelParameter) = GetModel(modelDirectory, modelName, predictorModelName); _Model = model; _PredictorModel = predictorModel; _ModelParameter = modelParameter; ConstructorInfo? constructorInfo = typeof(PropertyItem).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public, null, Array.Empty(), null) ?? throw new Exception(); _ConstructorInfo = constructorInfo; _WriteIndentedAndWhenWritingNull = new JsonSerializerOptions { WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }; } public override string ToString() { string result = JsonSerializer.Serialize(this, new JsonSerializerOptions() { WriteIndented = true }); return result; } public void Update(string dResultsFullGroupDirectory) { _FileGroups.Clear(); Dictionary keyValuePairs = Shared.Models.Stateless.Methods.IPath.GetKeyValuePairs(_PropertyConfiguration, dResultsFullGroupDirectory, new string[] { _PropertyConfiguration.ResultCollection, _PropertyConfiguration.ResultContent }); foreach (KeyValuePair keyValuePair in keyValuePairs) _FileGroups.Add(keyValuePair.Key, keyValuePair.Value); } private static (Model model, PredictorModel predictorModel, ModelParameter modelParameter) GetModel(string modelDirectory, string modelName, string predictorModelName) { (Model, PredictorModel, ModelParameter) result; Array array; Model? model = null; PredictorModel? predictorModel = null; array = Enum.GetValues(typeof(Model)); foreach (Model check in array) { if (modelName.Contains(check.ToString())) { model = check; break; } } if (model is null) throw new Exception("Destination directory must have Model name!"); model = model.Value; array = Enum.GetValues(typeof(PredictorModel)); foreach (PredictorModel check in array) { if (predictorModelName.Contains(check.ToString())) { predictorModel = check; break; } } if (predictorModel is null) throw new Exception("Destination directory must have Predictor Model name!"); predictorModel = predictorModel.Value; ModelParameter modelParameter = new() { CnnFaceDetectorModel = File.ReadAllBytes(Path.Combine(modelDirectory, "mmod_human_face_detector.dat")), FaceRecognitionModel = File.ReadAllBytes(Path.Combine(modelDirectory, "dlib_face_recognition_resnet_model_v1.dat")), PosePredictor5FaceLandmarksModel = File.ReadAllBytes(Path.Combine(modelDirectory, "shape_predictor_5_face_landmarks.dat")), PosePredictor68FaceLandmarksModel = File.ReadAllBytes(Path.Combine(modelDirectory, "shape_predictor_68_face_landmarks.dat")) }; result = new(model.Value, predictorModel.Value, modelParameter); return result; } #pragma warning disable CA1416 private void SaveFaces(FileHolder resizedFileHolder, List<(Shared.Models.Face, FileInfo?, string, bool)> collection) { int width; int height; Bitmap bitmap; short type = 2; Graphics graphics; Location? location; Rectangle rectangle; string locationJson; string faceEncodingJson; PropertyItem? propertyItem; string outputResolutionJson; int artist = (int)IExif.Tags.Artist; int fileSource = (int)IExif.Tags.FileSource; int userComment = (int)IExif.Tags.UserComment; using Bitmap source = new(resizedFileHolder.FullName); foreach ((Shared.Models.Face face, FileInfo? fileInfo, string fileName, bool save) in collection) { if (!save) continue; if (fileInfo is null) continue; if (face.FaceEncoding is null || face?.Location is null || face?.OutputResolution is null) 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); using (bitmap = new(width, height)) { using (graphics = Graphics.FromImage(bitmap)) graphics.DrawImage(source, new Rectangle(0, 0, width, height), rectangle, GraphicsUnit.Pixel); propertyItem = Property.Models.Stateless.IProperty.GetPropertyItem(_ConstructorInfo, fileSource, type, locationJson); bitmap.SetPropertyItem(propertyItem); propertyItem = Property.Models.Stateless.IProperty.GetPropertyItem(_ConstructorInfo, artist, type, outputResolutionJson); bitmap.SetPropertyItem(propertyItem); propertyItem = Property.Models.Stateless.IProperty.GetPropertyItem(_ConstructorInfo, userComment, type, faceEncodingJson); bitmap.SetPropertyItem(propertyItem); bitmap.Save(fileInfo.FullName, _ImageCodecInfo, _EncoderParameters); } if (File.Exists(fileName)) File.Delete(fileName); location = Shared.Models.Stateless.Methods.ILocation.GetLocation(_FaceDistanceHiddenImageFactor, 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; rectangle = new Rectangle(location.Left, location.Top, width, height); using (bitmap = new(width, height)) { using (graphics = Graphics.FromImage(bitmap)) graphics.DrawImage(source, new Rectangle(0, 0, width, height), rectangle, GraphicsUnit.Pixel); bitmap.Save(fileName, _HiddenImageCodecInfo, _HiddenEncoderParameters); } File.SetAttributes(fileName, FileAttributes.Hidden); } } private List GetFaces(string outputResolution, Shared.Models.Property property, MappingFromItem mappingFromItem, Dictionary outputResolutionToResize, List? locations) { if (_Log is null) throw new NullReferenceException(nameof(_Log)); if (_PropertyConfiguration.NumberOfJitters is null) throw new NullReferenceException(nameof(_PropertyConfiguration.NumberOfJitters)); if (_PropertyConfiguration.NumberOfTimesToUpsample is null) throw new NullReferenceException(nameof(_PropertyConfiguration.NumberOfTimesToUpsample)); List results = new(); FaceRecognitionDotNet.Image? unknownImage; try { unknownImage = FaceRecognition.LoadImageFile(mappingFromItem.ResizedFileHolder.FullName); } catch (Exception) { unknownImage = null; _Log.Info(string.Concat(new StackFrame().GetMethod()?.Name, " <", mappingFromItem.ResizedFileHolder.FullName, ">")); } if (unknownImage is not null) { (int outputResolutionWidth, int outputResolutionHeight, int outputResolutionOrientation) = Resize.Models.Stateless.Methods.IResize.Get(outputResolution, outputResolutionToResize); List<(Location Location, FaceRecognitionDotNet.FaceEncoding? FaceEncoding, Dictionary? FaceParts)> collection; FaceRecognition faceRecognition = new(_PropertyConfiguration.NumberOfJitters.Value, _PropertyConfiguration.NumberOfTimesToUpsample.Value, _Model, _ModelParameter, _PredictorModel); collection = faceRecognition.GetCollection(unknownImage, locations, includeFaceEncoding: true, includeFaceParts: true); if (!collection.Any()) results.Add(new(property, outputResolutionWidth, outputResolutionHeight, outputResolutionOrientation, location: null)); else { double[] rawEncoding; Shared.Models.Face face; Shared.Models.FaceEncoding convertedFaceEncoding; foreach ((Location location, FaceRecognitionDotNet.FaceEncoding? faceEncoding, Dictionary? faceParts) in collection) { face = new(property, outputResolutionWidth, outputResolutionHeight, outputResolutionOrientation, location); if (faceEncoding is not null) { rawEncoding = faceEncoding.GetRawEncoding(); convertedFaceEncoding = new(rawEncoding, faceEncoding.Size); face.SetFaceEncoding(convertedFaceEncoding); } if (faceParts is not null) face.SetFaceParts(faceParts); results.Add(face); } } unknownImage.Dispose(); faceRecognition.Dispose(); } return results; } #pragma warning restore CA1416 private static List> GetLocationContainers(string outputResolution, List> locationContainers, Dictionary outputResolutionToResize, List faces) { List> results = new(); string? json; Location? location; Rectangle? rectangle; List skip = new(); 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.Directories 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.Directories); 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.FromDistanceContent, locationContainer.File, locationContainer.PersonKey, locationContainer.Id, locationContainer.WholePercentages, locationContainer.Directories, rectangle.Value, location)); } } if (results.Any()) outputResolutionCheck = null; return results; } public List GetFaces(string outputResolution, string dResultsFullGroupDirectory, List> subFileTuples, List parseExceptions, Shared.Models.Property property, MappingFromItem mappingFromItem, Dictionary outputResolutionToResize, List>? locationContainers, List? mappingFromPhotoPrismCollection) { List? results; if (string.IsNullOrEmpty(dResultsFullGroupDirectory)) throw new NullReferenceException(nameof(dResultsFullGroupDirectory)); string? json; List? locations; string[] changesFrom = new string[] { 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.ResultAllInOneSubdirectoryLength, mappingFromItem.ImageFileHolder.Name); FileInfo fileInfo = new(Path.Combine(_FileGroups[_PropertyConfiguration.ResultCollection][directoryIndex], $"{mappingFromItem.ImageFileHolder.NameWithoutExtension}{mappingFromItem.ImageFileHolder.ExtensionLowered}.json")); if (_ForceFaceLastWriteTimeToCreationTime && !fileInfo.Exists && File.Exists(Path.ChangeExtension(fileInfo.FullName, ".delete"))) { File.Move(Path.ChangeExtension(fileInfo.FullName, ".delete"), fileInfo.FullName); fileInfo.Refresh(); } if (_ForceFaceLastWriteTimeToCreationTime && fileInfo.Exists && fileInfo.LastWriteTime != fileInfo.CreationTime) { File.SetLastWriteTime(fileInfo.FullName, fileInfo.CreationTime); fileInfo.Refresh(); } if (_PropertiesChangedForFaces) results = null; else if (!fileInfo.Exists) results = null; else if (_CheckDFaceAndUpWriteDates && dateTimes.Any() && dateTimes.Max() > fileInfo.LastWriteTime) results = null; else { json = Shared.Models.Stateless.Methods.IFace.GetJson(fileInfo.FullName); try { results = JsonSerializer.Deserialize>(json); if (results is null) throw new NullReferenceException(nameof(results)); subFileTuples.Add(new Tuple(nameof(D_Face), fileInfo.LastWriteTime)); } catch (Exception) { results = null; parseExceptions.Add(nameof(D_Face)); } } List> collection; if (results is null || locationContainers is null) collection = new(); 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(); else locations = Shared.Models.Stateless.Methods.ILocation.GetLocations(collection, results, mappingFromPhotoPrismCollection, _RectangleIntersectMinimum); if (results is null || (locations is not null && locations.Any())) { results = GetFaces(outputResolution, property, mappingFromItem, outputResolutionToResize, locations); if (!results.Any()) File.Move(mappingFromItem.ResizedFileHolder.FullName, $"{mappingFromItem.ResizedFileHolder.FullName}.err"); else { bool updateDateWhenMatches = dateTimes.Any() && fileInfo.Exists && dateTimes.Max() > fileInfo.LastWriteTime; DateTime? dateTime = !updateDateWhenMatches ? null : dateTimes.Max(); json = JsonSerializer.Serialize(results, _WriteIndentedAndWhenWritingNull); if (Shared.Models.Stateless.Methods.IPath.WriteAllText(fileInfo.FullName, json, updateDateWhenMatches, compareBeforeWrite: true, updateToWhenMatches: dateTime)) { if (!_ForceFaceLastWriteTimeToCreationTime) subFileTuples.Add(new Tuple(nameof(D_Face), DateTime.Now)); else { File.SetLastWriteTime(fileInfo.FullName, fileInfo.CreationTime); fileInfo.Refresh(); subFileTuples.Add(new Tuple(nameof(D_Face), fileInfo.CreationTime)); } } } } return results; } public List<(Shared.Models.Face, FileInfo?, string, bool)> SaveFaces(string f, string dResultsFullGroupDirectory, List> subFileTuples, List parseExceptions, MappingFromItem mappingFromItem, List faces) { List<(Shared.Models.Face, FileInfo?, string, bool Save)> results = new(); bool save; FileInfo fileInfo; string deterministicHashCodeKey; string[] changesFrom = new string[] { 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.ResultAllInOneSubdirectoryLength, mappingFromItem.ImageFileHolder.Name); string directory = Path.Combine(_FileGroups[_PropertyConfiguration.ResultContent][directoryIndex], mappingFromItem.ImageFileHolder.NameWithoutExtension); bool directoryExists = Directory.Exists(directory); foreach (Shared.Models.Face face in faces) { save = false; if (face.FaceEncoding is null || face.Location is null || face.OutputResolution is null) { results.Add(new(face, null, string.Empty, save)); continue; } deterministicHashCodeKey = Shared.Models.Stateless.Methods.IMapping.GetDeterministicHashCodeKey(mappingFromItem.Id, face.Location, Shared.Models.Stateless.ILocation.Digits, face.OutputResolution); fileInfo = new FileInfo(Path.Combine(directory, $"{deterministicHashCodeKey}{mappingFromItem.ImageFileHolder.ExtensionLowered}{_FileNameExtension}")); if (!directoryExists) save = true; else if (_OverrideForFaceImages) save = true; else if (!fileInfo.Exists) save = true; else if (_CheckDFaceAndUpWriteDates && dateTimes.Any() && dateTimes.Max() > fileInfo.LastWriteTime) save = true; results.Add(new(face, fileInfo, Path.Combine(directory, $"{deterministicHashCodeKey}{mappingFromItem.ImageFileHolder.ExtensionLowered}{_HiddenFileNameExtension}"), save)); } if (results.Any(l => l.Save)) { if (!directoryExists) _ = Directory.CreateDirectory(directory); SaveFaces(mappingFromItem.ResizedFileHolder, results); } return results; } }