using System.Diagnostics; using System.Drawing; using System.Drawing.Imaging; using System.Globalization; using System.Reflection; using System.Runtime.InteropServices; using System.Text; using System.Text.RegularExpressions; using View_by_Distance.Shared.Models; using View_by_Distance.Shared.Models.Methods; namespace View_by_Distance.Property.Models.Stateless; internal partial class Property { internal static TimeSpan GetThreeStandardDeviationHigh(int minimum, Container.Models.Container container) { TimeSpan result; DateTime? minimumDateTime; List ticksCollection = []; foreach (Item item in container.Items) { if (item.ExifDirectory is null) continue; minimumDateTime = Shared.Models.Stateless.Methods.IDate.GetMinimum(item.ExifDirectory); if (minimumDateTime is null) continue; ticksCollection.Add(minimumDateTime.Value.Ticks); } long threeStandardDeviationHigh; long min; if (ticksCollection.Count == 0) min = 0; else min = ticksCollection.Min(); if (ticksCollection.Count < minimum) threeStandardDeviationHigh = long.MaxValue; else threeStandardDeviationHigh = GetThreeStandardDeviationHigh(ref ticksCollection, min); result = new TimeSpan(threeStandardDeviationHigh - min); return result; } private static long GetThreeStandardDeviationHigh(ref List ticksCollection, long min) { long result; ticksCollection = (from l in ticksCollection select l - min).ToList(); double sum = ticksCollection.Sum(); double average = sum / ticksCollection.Count; double standardDeviation = GetStandardDeviation(ticksCollection, average); result = (long)Math.Ceiling(average + min + (standardDeviation * 3)); return result; } internal static double GetStandardDeviation(List values, double average) { double result = 0; 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); return result; } internal static byte[] GetBytes(string value) { byte[] results = new byte[value.Length + 1]; for (int i = 0; i < value.Length; i++) results[i] = (byte)value[i]; results[value.Length] = 0x00; return results; } internal static DateTime? GetDateTimeFromName(FilePath filePath) { DateTime? result = null; int length; string format; string fullFormat; StringBuilder value = new(); const string ticksExample = "##################"; string[][] dateFormats = [ [string.Empty, "yyyyMMdd_HHmmss", string.Empty], [string.Empty, "yyyyMMddHHmmssfff", string.Empty], [string.Empty, "yyyyMMdd_", ticksExample], [string.Empty, "yyyy-MM-dd_", ticksExample], [string.Empty, "yyyy-MM-dd.", ticksExample], [string.Empty, "yyyy-MM-dd.", $"{ticksExample}.{filePath.Length}"], [string.Empty, "yyyy-MM-dd HH.mm.ss", string.Empty], [string.Empty, "yyyyMMdd_HHmmss", "_LLS"], [string.Empty, "yyyyMMdd_HHmmss", "_HDR"], ["WIN_", "yyyyMMdd_HH_mm_ss", "_Pro"], ["IMG_", "yyyyMMdd_HHmmss", string.Empty], ["IMG#####-", "yyyyMMdd-HHmm", string.Empty], ["CameraZOOM-", "yyyyMMddHHmmss", string.Empty], ["VideoCapture_", "yyyyMMdd-HHmmss ", string.Empty] ]; foreach (string[] dateFormat in dateFormats) { _ = value.Clear(); if (dateFormat.Length != 3) throw new Exception(); fullFormat = string.Join(string.Empty, dateFormat); if (filePath.NameWithoutExtension.Length != fullFormat.Length) continue; format = dateFormat[1]; length = dateFormat[0].Length + dateFormat[1].Length; for (int i = dateFormat[0].Length; i < length; i++) _ = value.Append(filePath.FileNameFirstSegment[i]); if (value.Length != format.Length) continue; if (DateTime.TryParseExact(value.ToString(), format, CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime checkDateTime)) { if (filePath.NameWithoutExtension.Length < ticksExample.Length || !long.TryParse(filePath.FileNameFirstSegment[^ticksExample.Length..], out long ticks)) result = checkDateTime; else result = new DateTime(ticks); break; } } return result; } internal static bool Any(Container.Models.Container[] containers) { bool result = false; foreach (Container.Models.Container container in containers) { if (container.Items.Count == 0) continue; if ((from l in container.Items where l.Any() select true).Any()) { result = true; break; } } return result; } internal static DateTime? GetDateTime(string dateTimeFormat, string? value) { DateTime? result; string alternateFormat = "ddd MMM dd HH:mm:ss yyyy"; if (value is not null && DateTime.TryParse(value, out DateTime dateTime)) result = dateTime; else if (value is not null && value.Length == dateTimeFormat.Length && DateTime.TryParseExact(value, dateTimeFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out dateTime)) result = dateTime; else if (value is not null && value.Length == alternateFormat.Length && DateTime.TryParseExact(value, alternateFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out dateTime)) result = dateTime; else result = null; return result; } internal static PropertyItem GetPropertyItem(ConstructorInfo constructorInfo, int id, short type, string value) { #pragma warning disable CA1416 PropertyItem result = (PropertyItem)constructorInfo.Invoke(null); int length; byte[] bytes; if (type == 2) { bytes = GetBytes(value); length = value.Length + 1; } else if (type == 1) { bytes = Encoding.Unicode.GetBytes($"{value}\0"); length = bytes.Length; } else throw new NotSupportedException(); result.Id = id; result.Len = length; result.Type = type; result.Value = bytes; #pragma warning restore CA1416 return result; } internal static (int, List, List) Get(Container.Models.Container container, TimeSpan threeStandardDeviationHigh, int i) { List results = []; int j = i; long? ticks; TimeSpan timeSpan; Item item; DateTime? minimumDateTime; Item nextItem; DateTime? nextMinimumDateTime; List dateTimes = []; for (; j < container.Items.Count; j++) { ticks = null; item = container.Items[j]; if (item.ExifDirectory is null) continue; minimumDateTime = Shared.Models.Stateless.Methods.IDate.GetMinimum(item.ExifDirectory); if (minimumDateTime is null) continue; for (int k = j + 1; k < container.Items.Count; k++) { nextItem = container.Items[k]; if (nextItem.ExifDirectory is null) continue; nextMinimumDateTime = Shared.Models.Stateless.Methods.IDate.GetMinimum(nextItem.ExifDirectory); if (nextMinimumDateTime is null) continue; ticks = nextMinimumDateTime.Value.Ticks; break; } results.Add(item); dateTimes.Add(minimumDateTime.Value); if (ticks.HasValue) { timeSpan = new(ticks.Value - minimumDateTime.Value.Ticks); if (timeSpan > threeStandardDeviationHigh) break; } } return new(j, dateTimes, results); } internal static (DateTime?, DateTime[], int?, string?) Get(bool populateId, IMetadata? metadata, FilePath filePath, bool isIgnoreExtension, bool isValidImageFormatExtension, ASCIIEncoding asciiEncoding) { int? id = null; string? message; DateTime[] dateTimes; Shared.Models.Property? property = null; if (isIgnoreExtension || !isValidImageFormatExtension) (message, dateTimes, property) = (null, [], null); else (message, dateTimes, property) = GetProperty(populateId, metadata, filePath, property, isIgnoreExtension, isValidImageFormatExtension, id, asciiEncoding); return new(property?.DateTimeOriginal, dateTimes, property?.Id, message); } internal static (string?, DateTime[], Shared.Models.Property) GetProperty(bool populateId, IMetadata? metadata, FilePath filePath, Shared.Models.Property? property, bool isIgnoreExtension, bool isValidImageFormatExtension, int? id, ASCIIEncoding asciiEncoding) { Shared.Models.Property result; byte[] bytes; string value; string? message; int? width = null; int? height = null; string? make = null; string? model = null; DateTime?[] dateTimes; string dateTimeFormat; DateTime checkDateTime; int? orientation = null; DateTime? dateTime = null; string[]? keywords = null; PropertyItem? propertyItem; DateTime? gpsDateStamp = null; List dateTimesByLogic; DateTime? dateTimeOriginal = null; DateTime? dateTimeDigitized = null; DateTime? dateTimeOriginalByLogic = null; DateTime? dateTimeFromName = GetDateTimeFromName(filePath); if (!isValidImageFormatExtension && metadata is not null) { try { IReadOnlyList directories = MetadataExtractor.ImageMetadataReader.ReadMetadata(filePath.FullName); (dateTimeOriginalByLogic, DateTime?[] metadataDateTimes) = metadata.GetDateTimes(filePath, directories); dateTimesByLogic = GetDateTimes(metadataDateTimes); message = null; } catch (Exception) { dateTimesByLogic = []; message = string.Concat(new StackFrame().GetMethod()?.Name, " <", filePath.FullName, ">"); } } else if (!isIgnoreExtension && isValidImageFormatExtension) { try { #pragma warning disable CA1416 using Image image = Image.FromFile(filePath.FullName); width = image.Width; height = image.Height; if (populateId && id is null) { using Bitmap bitmap = new(image); Rectangle rectangle = new(0, 0, image.Width, image.Height); BitmapData bitmapData = bitmap.LockBits(rectangle, ImageLockMode.ReadOnly, bitmap.PixelFormat); IntPtr intPtr = bitmapData.Scan0; int length = bitmapData.Stride * bitmap.Height; bytes = new byte[length]; Marshal.Copy(intPtr, bytes, 0, length); bitmap.UnlockBits(bitmapData); id ??= Shared.Models.Stateless.Methods.IId.GetDeterministicHashCode(bytes); } dateTimeFormat = IProperty.DateTimeFormat; if (image.PropertyIdList.Contains(MetadataExtractor.Formats.Exif.ExifDirectoryBase.TagDateTime)) { propertyItem = image.GetPropertyItem(MetadataExtractor.Formats.Exif.ExifDirectoryBase.TagDateTime); if (propertyItem?.Value is not null) { value = asciiEncoding.GetString(propertyItem.Value, 0, propertyItem.Len - 1); if (value.Length > dateTimeFormat.Length) value = value[..dateTimeFormat.Length]; if (value.Length == dateTimeFormat.Length && DateTime.TryParseExact(value, dateTimeFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out checkDateTime)) dateTime = checkDateTime; } } if (image.PropertyIdList.Contains(MetadataExtractor.Formats.Exif.ExifDirectoryBase.TagDateTimeDigitized)) { propertyItem = image.GetPropertyItem(MetadataExtractor.Formats.Exif.ExifDirectoryBase.TagDateTimeDigitized); if (propertyItem?.Value is not null) { value = asciiEncoding.GetString(propertyItem.Value, 0, propertyItem.Len - 1); if (value.Length > dateTimeFormat.Length) value = value[..dateTimeFormat.Length]; if (value.Length == dateTimeFormat.Length && DateTime.TryParseExact(value, dateTimeFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out checkDateTime)) dateTimeDigitized = checkDateTime; } } if (image.PropertyIdList.Contains(MetadataExtractor.Formats.Exif.ExifDirectoryBase.TagDateTimeOriginal)) { propertyItem = image.GetPropertyItem(MetadataExtractor.Formats.Exif.ExifDirectoryBase.TagDateTimeOriginal); if (propertyItem?.Value is not null) { value = asciiEncoding.GetString(propertyItem.Value, 0, propertyItem.Len - 1); if (value.Length > dateTimeFormat.Length) value = value[..dateTimeFormat.Length]; if (value.Length == dateTimeFormat.Length && DateTime.TryParseExact(value, dateTimeFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out checkDateTime)) dateTimeOriginal = checkDateTime; } } if (image.PropertyIdList.Contains(MetadataExtractor.Formats.Exif.GpsDirectory.TagDateStamp)) { propertyItem = image.GetPropertyItem(MetadataExtractor.Formats.Exif.GpsDirectory.TagDateStamp); if (propertyItem?.Value is not null) { value = asciiEncoding.GetString(propertyItem.Value, 0, propertyItem.Len - 1); if (value.Length > dateTimeFormat.Length) value = value[..dateTimeFormat.Length]; if (value.Length == dateTimeFormat.Length && DateTime.TryParseExact(value, dateTimeFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out checkDateTime)) gpsDateStamp = checkDateTime; } } if (image.PropertyIdList.Contains(MetadataExtractor.Formats.Exif.ExifDirectoryBase.TagMake)) { propertyItem = image.GetPropertyItem(MetadataExtractor.Formats.Exif.ExifDirectoryBase.TagMake); if (propertyItem?.Value is not null) make = asciiEncoding.GetString(propertyItem.Value, 0, propertyItem.Len - 1); } if (image.PropertyIdList.Contains(MetadataExtractor.Formats.Exif.ExifDirectoryBase.TagModel)) { propertyItem = image.GetPropertyItem(MetadataExtractor.Formats.Exif.ExifDirectoryBase.TagModel); if (propertyItem?.Value is not null) model = asciiEncoding.GetString(propertyItem.Value, 0, propertyItem.Len - 1); } if (image.PropertyIdList.Contains(MetadataExtractor.Formats.Exif.ExifDirectoryBase.TagOrientation)) { propertyItem = image.GetPropertyItem(MetadataExtractor.Formats.Exif.ExifDirectoryBase.TagOrientation); if (propertyItem?.Value is not null) orientation = BitConverter.ToInt16(propertyItem.Value, 0); } if (image.PropertyIdList.Contains(MetadataExtractor.Formats.Exif.ExifDirectoryBase.TagWinKeywords)) { propertyItem = image.GetPropertyItem(MetadataExtractor.Formats.Exif.ExifDirectoryBase.TagWinKeywords); if (propertyItem?.Value is not null) { if (propertyItem.Type == 2) keywords = asciiEncoding.GetString(propertyItem.Value).Trim('\0', ' ').Split(';'); else if (propertyItem.Type == 1) keywords = Encoding.Unicode.GetString(propertyItem.Value).Trim('\0', ' ').Split(';'); } } #pragma warning restore CA1416 message = null; dateTimes = [new(filePath.LastWriteTicks), new(filePath.CreationTicks), dateTime, dateTimeDigitized, dateTimeOriginal, gpsDateStamp]; } catch (Exception) { dateTimes = []; message = string.Concat(new StackFrame().GetMethod()?.Name, " <", filePath.FullName, ">"); } if (metadata is not null && dateTimeOriginal is null) { try { IReadOnlyList directories = MetadataExtractor.ImageMetadataReader.ReadMetadata(filePath.FullName); (dateTimeOriginalByLogic, DateTime?[] metadataDateTimes) = metadata.GetDateTimes(filePath, directories); dateTimesByLogic = GetDateTimes(dateTimes, metadataDateTimes); message = null; } catch (Exception) { message = string.Concat(new StackFrame().GetMethod()?.Name, " <", filePath.FullName, ">"); } } if (dateTimeFromName is null) (dateTimeOriginalByLogic, dateTimesByLogic) = (dateTimeOriginal, GetDateTimes(filePath, dateTimes)); else (dateTimeOriginalByLogic, dateTimesByLogic) = (dateTimeOriginal, GetDateTimes(dateTimeFromName.Value, dateTimes)); } else (message, dateTimeOriginalByLogic, dateTimesByLogic) = (null, null, []); if (property is not null) result = new(property.CreationTime, dateTime, dateTimeDigitized, dateTimeFromName, dateTimeOriginalByLogic, filePath.Length, gpsDateStamp, height, id, keywords, property.LastWriteTime, make, model, orientation?.ToString(), width); else result = new(new(filePath.CreationTicks), dateTime, dateTimeDigitized, dateTimeFromName, dateTimeOriginalByLogic, filePath.Length, gpsDateStamp, height, id, keywords, new(filePath.LastWriteTicks), make, model, orientation?.ToString(), width); return (message, dateTimesByLogic.ToArray(), result); } private static List GetDateTimes(DateTime?[] metadataDateTimes) { List results = []; foreach (DateTime? dateTime in metadataDateTimes) { if (dateTime is null || results.Contains(dateTime.Value)) continue; results.Add(dateTime.Value); } return results; } private static List GetDateTimes(FilePath filePath, DateTime?[] dateTimes) { List results = []; string[] digits = Digit().Split(filePath.FullName); foreach (string digit in digits) { if (digit.Length != 4 || digit[..2] is not "19" and not "20" || !int.TryParse(digit, out int year)) continue; results.Add(new(year, 1, 1)); } foreach (DateTime? dateTime in dateTimes) { if (dateTime is null) continue; results.Add(dateTime.Value); } return results; } private static List GetDateTimes(DateTime dateTimeFromName, DateTime?[] dateTimes) { List results = [dateTimeFromName]; foreach (DateTime? dateTime in dateTimes) { if (dateTime is null) continue; results.Add(dateTime.Value); } return results; } private static List GetDateTimes(DateTime?[] dateTimes, DateTime?[] metadataDateTimes) { List results = []; foreach (DateTime? dateTime in metadataDateTimes) { if (dateTime is null || results.Contains(dateTime.Value)) continue; results.Add(dateTime.Value); } foreach (DateTime? dateTime in dateTimes) { if (dateTime is null || results.Contains(dateTime.Value)) continue; results.Add(dateTime.Value); } return results; } [GeneratedRegex(@"\D+")] private static partial Regex Digit(); }