using System.Collections.ObjectModel; using System.Diagnostics; using System.Text.Json; using Microsoft.Extensions.Logging; using Phares.Metadata.Models.Stateless; using Phares.Shared.Models; namespace File_Folder_Helper.ADO2025.PI6; internal static partial class Helper20250720 { internal static void WriteFaceData(ILogger logger, List args) { logger.LogInformation(args[0]); logger.LogInformation(args[1]); logger.LogInformation(args[2]); logger.LogInformation(args[3]); logger.LogInformation(args[4]); logger.LogInformation(args[5]); logger.LogInformation(args[6]); logger.LogInformation(args[6]); logger.LogInformation(args[7]); logger.LogInformation(args[8]); string searchPattern = args[5]; string searchPatternXMP = args[7]; string outputDirectoryName = args[8]; string jsonFile = Path.GetFullPath(args[3]); string digiKamFile = Path.GetFullPath(args[6]); FileInfo faceFileInfo = new(Path.GetFullPath(args[4])); string sourceDirectory = Path.GetFullPath(args[0].Split('~')[0]); string pathRoot = Path.GetPathRoot(faceFileInfo.FullName) ?? throw new Exception(); string? checkDirectory = Path.GetDirectoryName(digiKamFile.Replace("{}", outputDirectoryName)); string originalFile = Path.Combine(sourceDirectory, args[2]); string pathRootXMP = digiKamFile[..5]; if (!File.Exists(jsonFile)) { logger.LogError("json file doesn't exist! <{jsonFile}>", jsonFile); } else if (!File.Exists(digiKamFile)) { logger.LogError("digiKam file doesn't exist! <{digiKamFile}>", digiKamFile); } else if (!File.Exists(originalFile)) { logger.LogError("Original file doesn't exist! <{checkFile}>", originalFile); } else if (!Directory.Exists(checkDirectory)) { logger.LogError("checkDirectory doesn't exist! <{checkDirectory}>", checkDirectory); } else if (!faceFileInfo.Exists) { logger.LogError("Face file doesn't exist! <{faceFileInfo}>", faceFileInfo.FullName); } else { string json = File.ReadAllText(jsonFile); ResultSettings? resultSettings = JsonSerializer.Deserialize(json, ResultSettingsSourceGenerationContext.Default.ResultSettings); MetadataSettings? metadataSettings = JsonSerializer.Deserialize(json, MetadataSettingsSourceGenerationContext.Default.MetadataSettings); if (resultSettings is null) { logger.LogError(nameof(ResultSettings)); } else if (metadataSettings is null) { logger.LogError(nameof(MetadataSettings)); } else { WriteFaceData(logger, outputDirectoryName, originalFile, faceFileInfo, digiKamFile, resultSettings, metadataSettings); ReadOnlyDictionary> keyValuePairsXMP = GetKeyValuePairs(searchPatternXMP, pathRootXMP); ReadOnlyDictionary> keyValuePairs = GetKeyValuePairs(searchPattern, pathRoot, resultSettings, metadataSettings); if (keyValuePairs.Count == 0) { logger.LogError("Didn't find any valid file(s)!"); } else { WriteFaceData(logger, outputDirectoryName, keyValuePairs, keyValuePairsXMP); } } } } private static void WriteFaceData(ILogger logger, string outputDirectoryName, string originalFile, FileInfo faceFileInfo, string digiKamFile, ResultSettings resultSettings, MetadataSettings metadataSettings) { ExifDirectory? exifDirectory = IMetadata.GetExifDirectory(resultSettings, metadataSettings, faceFileInfo); if (exifDirectory is null) { logger.LogError("exifDirectory is null!"); } else { long id; string fileNameWithoutExtension; fileNameWithoutExtension = Path.GetFileNameWithoutExtension(faceFileInfo.FullName); if (!long.TryParse(fileNameWithoutExtension.Split('.')[0], out id)) { id = -1; } long? fileNameFirstSegment = id == -1 ? null : id; ReadOnlyCollection digiKamFiles = new([digiKamFile]); ReadOnlyCollection exifDirectories = new([exifDirectory]); WriteFaceData(logger, outputDirectoryName, originalFile, fileNameFirstSegment, exifDirectories, digiKamFiles); } } private static void WriteFaceData(ILogger logger, string outputDirectoryName, string? originalFile, long? fileNameFirstSegment, ReadOnlyCollection exifDirectories, ReadOnlyCollection digiKamFiles) { ReadOnlyDictionary keyValuePairs = GetKeyValuePairs(logger, exifDirectories); if (keyValuePairs.Count > 0) { WriteFaceData(logger, outputDirectoryName, originalFile, fileNameFirstSegment, digiKamFiles, keyValuePairs); } } private static int? GetMatchingLine(string digiKamLine, ReadOnlyCollection lines) { int? result = null; for (int i = 0; i < lines.Count; i++) { if (lines[i] == digiKamLine) { result = i; break; } } return result; } private static void WriteFaceData(string outputDirectoryName, string? originalFile, ReadOnlyDictionary keyValuePairs, string digiKamFile, List trimmed, int rdfLine) { if (keyValuePairs.Count == 0) { throw new Exception(); } double h; double w; double mpTop; double width; double height; double mpLeft; double mwgTop; double mwgLeft; string personKey; FaceFile faceFile; string descriptionLine = ""; faceFile = keyValuePairs.ElementAt(0).Value; List regionLines = [ "", "", "" ]; List regionsLinesB = [ "", "", "", "" ]; List digiKamLines = ["", ""]; List microsoftPhotoLines = ["", ""]; List hierarchicalSubjectLines = ["", ""]; List catalogSetLines = ["", ""]; List subjectLines = ["", ""]; foreach (KeyValuePair keyValuePair in keyValuePairs) { personKey = keyValuePair.Key; faceFile = keyValuePair.Value; width = faceFile.Location.Right - faceFile.Location.Left; height = faceFile.Location.Bottom - faceFile.Location.Top; if (!string.IsNullOrEmpty(originalFile) && File.Exists(originalFile)) { Extract(file: originalFile, width: width, height: height, left: faceFile.Location.Left, top: faceFile.Location.Top, suffix: $"-{keyValuePair.Key}-whole-percentages.jpg"); } w = width / faceFile.OutputResolution.Width; h = height / faceFile.OutputResolution.Height; if (w == 0 || h == 0) throw new NotImplementedException(); mpLeft = (double)faceFile.Location.Left / faceFile.OutputResolution.Width; mpTop = (double)faceFile.Location.Top / faceFile.OutputResolution.Height; mwgLeft = (faceFile.Location.Left + (width * .5)) / faceFile.OutputResolution.Width; mwgTop = (faceFile.Location.Top + (height * .5)) / faceFile.OutputResolution.Height; // // // // // // // regionLines.Add(""); // // // // // // // // // // // // regionsLinesB.Add(""); regionsLinesB.Add(""); regionsLinesB.Add(""); regionsLinesB.Add(""); regionsLinesB.Add(""); // // // People/{personKey} // // digiKamLines.Add($"People/{personKey}"); // // // People/{personKey} // // microsoftPhotoLines.Add($"People/{personKey}"); // // // People|{personKey} // // hierarchicalSubjectLines.Add($"People|{personKey}"); // // // People|{personKey} // // catalogSetLines.Add($"People|{personKey}"); // // // {personKey} // // subjectLines.Add($"{personKey}"); } regionLines.AddRange(["","",""]); regionsLinesB.AddRange(["", "",""]); digiKamLines.AddRange(["", ""]); microsoftPhotoLines.AddRange(["", ""]); hierarchicalSubjectLines.AddRange(["", ""]); catalogSetLines.AddRange(["", ""]); subjectLines.AddRange(["", ""]); List lines = []; lines.AddRange(regionLines); lines.AddRange(regionsLinesB); lines.AddRange(digiKamLines); lines.AddRange(microsoftPhotoLines); lines.AddRange(hierarchicalSubjectLines); lines.AddRange(catalogSetLines); lines.AddRange(subjectLines); string text = string.Join(Environment.NewLine, lines); if (trimmed[rdfLine - 1] != descriptionLine) { trimmed[rdfLine - 1] = $"{trimmed[rdfLine - 1][..^2]}>"; trimmed.Insert(rdfLine, descriptionLine); rdfLine++; } trimmed.Insert(rdfLine - 1, text); string allText = string.Join(Environment.NewLine, trimmed); File.WriteAllText(digiKamFile.Replace("{}", outputDirectoryName), allText); if (!string.IsNullOrEmpty(originalFile)) { if (Debugger.IsAttached) { File.WriteAllText(".xml", allText); } } } private static ReadOnlyDictionary> GetKeyValuePairs(string searchPattern, string pathRoot) { Dictionary> results = []; long fileNameFirstSegment; List? collection; string fileNameWithoutExtension; Dictionary> keyValuePairs = []; string[] files = Directory.GetFiles(pathRoot, searchPattern, SearchOption.AllDirectories); foreach (string file in files) { fileNameWithoutExtension = Path.GetFileNameWithoutExtension(file); if (!long.TryParse(fileNameWithoutExtension.Split('.')[0], out fileNameFirstSegment)) { continue; } if (!keyValuePairs.TryGetValue(fileNameFirstSegment, out collection)) { keyValuePairs.Add(fileNameFirstSegment, []); if (!keyValuePairs.TryGetValue(fileNameFirstSegment, out collection)) { throw new Exception(); } } collection.Add(file); } foreach (KeyValuePair> keyValuePair in keyValuePairs) { results.Add(keyValuePair.Key, keyValuePair.Value.AsReadOnly()); } return results.AsReadOnly(); } private static ReadOnlyDictionary GetKeyValuePairs(ILogger logger, ReadOnlyCollection exifDirectories) { Dictionary results = []; string personKey; FaceFile? faceFile; foreach (ExifDirectory exifDirectory in exifDirectories) { faceFile = IMetadata.GetFaceFile(exifDirectory); if (faceFile is null) { logger.LogError("faceFile is null!"); } else if (faceFile.Location is null) { logger.LogError("faceFile location is null!"); } else if (faceFile.OutputResolution?.Orientation is not 0 and not 1) { logger.LogWarning("faceFile output-resolution orientation is not aloud!"); } else { personKey = Path.GetFileName(Path.GetDirectoryName(Path.GetDirectoryName(Path.GetDirectoryName(exifDirectory.FilePath.FullName)))); if (results.ContainsKey(personKey)) { continue; } results.Add(personKey, faceFile); } } return results.AsReadOnly(); } private static ReadOnlyDictionary> GetKeyValuePairs(string searchPattern, string pathRoot, ResultSettings resultSettings, MetadataSettings metadataSettings) { Dictionary> results = []; FileInfo faceFileInfo; long fileNameFirstSegment; ExifDirectory? exifDirectory; List? collection; string fileNameWithoutExtension; Dictionary> keyValuePairs = []; string[] files = Directory.GetFiles(pathRoot, searchPattern, SearchOption.AllDirectories); foreach (string file in files) { faceFileInfo = new(file); fileNameWithoutExtension = Path.GetFileNameWithoutExtension(faceFileInfo.FullName); if (!long.TryParse(fileNameWithoutExtension.Split('.')[0], out fileNameFirstSegment)) { continue; } exifDirectory = IMetadata.GetExifDirectory(resultSettings, metadataSettings, faceFileInfo); if (exifDirectory is null) continue; if (!keyValuePairs.TryGetValue(fileNameFirstSegment, out collection)) { keyValuePairs.Add(fileNameFirstSegment, []); if (!keyValuePairs.TryGetValue(fileNameFirstSegment, out collection)) throw new Exception(); } collection.Add(exifDirectory); } foreach (KeyValuePair> keyValuePair in keyValuePairs) { results.Add(keyValuePair.Key, keyValuePair.Value.AsReadOnly()); } return results.AsReadOnly(); } private static void WriteFaceData(ILogger logger, string outputDirectoryName, ReadOnlyDictionary> keyValuePairs, ReadOnlyDictionary> keyValuePairsXMP) { ReadOnlyCollection? digiKamFiles; foreach (KeyValuePair> keyValuePair in keyValuePairs) { if (!keyValuePairsXMP.TryGetValue(keyValuePair.Key, out digiKamFiles)) { logger.LogWarning("{fileNameFirstSegment}) Didn't find a matching file!", keyValuePair.Key); } else { string? originalFile = null; WriteFaceData(logger, outputDirectoryName, originalFile, keyValuePair.Key, keyValuePair.Value, digiKamFiles); } } } private static void WriteFaceData(ILogger logger, string outputDirectoryName, string? originalFile, long? fileNameFirstSegment, ReadOnlyCollection digiKamFiles, ReadOnlyDictionary keyValuePairs) { #if xmp IXmpMeta xmp; using FileStream stream = File.OpenRead(digiKamFile); xmp = XmpMetaFactory.Parse(stream); foreach (var property in xmp.Properties) { logger.LogDebug("Path={property.Path} Namespace={property.Namespace} Value={property.Value}", property.Path, property.Namespace, property.Value); } xmp.Sort(); SerializeOptions serializeOptions = new(SerializeOptions.EncodeUtf8); string check = XmpMetaFactory.SerializeToString(xmp, serializeOptions); File.WriteAllText(".xmp", check); #endif string[] requiredLines = [ "xmlns:digiKam=\"http://www.digikam.org/ns/1.0/\"", "xmlns:xmp=\"http://ns.adobe.com/xap/1.0/\"", "xmlns:exif=\"http://ns.adobe.com/exif/1.0/\"", "xmlns:tiff=\"http://ns.adobe.com/tiff/1.0/\"", "xmlns:dc=\"http://purl.org/dc/elements/1.1/\"", "xmlns:acdsee=\"http://ns.acdsee.com/iptc/1.0/\"", "xmlns:lr=\"http://ns.adobe.com/lightroom/1.0/\"", "xmlns:MP=\"http://ns.microsoft.com/photo/1.2/\"", "xmlns:stArea=\"http://ns.adobe.com/xmp/sType/Area#\"", "xmlns:photoshop=\"http://ns.adobe.com/photoshop/1.0/\"", "xmlns:MicrosoftPhoto=\"http://ns.microsoft.com/photo/1.0/\"", "xmlns:MPReg=\"http://ns.microsoft.com/photo/1.2/t/Region#\"", "xmlns:stDim=\"http://ns.adobe.com/xap/1.0/sType/Dimensions#\"", "xmlns:MPRI=\"http://ns.microsoft.com/photo/1.2/t/RegionInfo#\"", "xmlns:mediapro=\"http://ns.iview-multimedia.com/mediapro/1.0/\"", "xmlns:mwg-rs=\"http://www.metadataworkinggroup.com/schemas/regions/\"", "" ]; foreach (string digiKamFile in digiKamFiles) { string[] lines = File.ReadAllLines(digiKamFile); List trimmed = lines.Select(l => l.Trim()).ToList(); int? digiKamLine = GetMatchingLine(requiredLines[0], trimmed.AsReadOnly()); if (digiKamLine is null) { logger.LogError("{fileNameFirstSegment}) Didn't fine digiKam line!", fileNameFirstSegment); } else { foreach (string requiredLine in requiredLines) { if (!trimmed.Contains(requiredLine)) { trimmed.Insert(digiKamLine.Value + 1, requiredLine); } } int? rdfLine = GetMatchingLine(requiredLines[^1], trimmed.AsReadOnly()); if (rdfLine is null) { logger.LogError("{fileNameFirstSegment}) Didn't fine description line!", fileNameFirstSegment); } else { WriteFaceData(outputDirectoryName, originalFile, keyValuePairs, digiKamFile, trimmed, rdfLine.Value); } } } } private static void Extract(string file, double width, double height, int left, int top, string suffix) { #if SystemDrawingCommon Rectangle rectangle = new(left, top, width, height); using (Bitmap source = new(file)) { using (Bitmap bitmap = new(width, height)) { using (Graphics graphics = Graphics.FromImage(bitmap)) { graphics.DrawImage(source, new Rectangle(0, 0, width, height), rectangle, GraphicsUnit.Pixel); } bitmap.Save($"{file}{suffix}"); } } #endif } }