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; using View_by_Distance.Shared.Models.Stateless; namespace View_by_Distance.Property.Models.Stateless; internal class Property { private static List GetDateTimes(DateTime dateTimeFromName, DateTime?[] dateTimes) { List results = new() { 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 = new(); 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; } private static List GetDateTimes(FileHolder fileHolder, DateTime?[] dateTimes) { List results = new(); string[] digits = Regex.Split(fileHolder.FullName, @"\D+"); 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; } 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(FileHolder fileHolder) { DateTime? result = null; int length; string format; string fullFormat; StringBuilder value = new(); const string ticksExample = "##################"; string[][] dateFormats = new string[][] { new string[] { string.Empty, "yyyyMMdd_HHmmss", string.Empty }, new string[] { string.Empty, "yyyyMMddHHmmssfff", string.Empty }, new string[] { string.Empty, "yyyyMMdd_", ticksExample }, new string[] { string.Empty, "yyyy-MM-dd_", ticksExample }, new string[] { string.Empty, "yyyy-MM-dd.", ticksExample }, new string[] { string.Empty, "yyyy-MM-dd.", $"{ticksExample}.{fileHolder.Length}" }, new string[] { string.Empty, "yyyy-MM-dd HH.mm.ss", string.Empty }, new string[] { string.Empty, "yyyyMMdd_HHmmss", "_LLS" }, new string[] { string.Empty, "yyyyMMdd_HHmmss", "_HDR" }, new string[] { "WIN_", "yyyyMMdd_HH_mm_ss", "_Pro" }, new string[] { "IMG_", "yyyyMMdd_HHmmss", string.Empty }, new string[] { "IMG#####-", "yyyyMMdd-HHmm", string.Empty }, new string[] { "CameraZOOM-", "yyyyMMddHHmmss", string.Empty }, new string[] { "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 (fileHolder.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(fileHolder.NameWithoutExtension[i]); if (value.Length != format.Length) continue; if (DateTime.TryParseExact(value.ToString(), format, CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime checkDateTime)) { if (fileHolder.NameWithoutExtension.Length < ticksExample.Length || !long.TryParse(fileHolder.NameWithoutExtension[^ticksExample.Length..], out long ticks)) result = checkDateTime; else result = new DateTime(ticks); break; } } return result; } private static List GetDateTimes(DateTime?[] metadataDateTimes) { List results = new(); foreach (DateTime? dateTime in metadataDateTimes) { if (dateTime is null || results.Contains(dateTime.Value)) continue; results.Add(dateTime.Value); } return results; } internal static int GetDeterministicHashCode(byte[] value) { int result; unchecked { int hash1 = (5381 << 16) + 5381; int hash2 = hash1; for (int i = 0; i < value.Length; i += 2) { hash1 = ((hash1 << 5) + hash1) ^ value[i]; if (i == value.Length - 1) break; hash2 = ((hash2 << 5) + hash2) ^ value[i + 1]; } result = hash1 + (hash2 * 1566083941); } return result; } #pragma warning disable CA1416 internal static PropertyItem GetPropertyItem(ConstructorInfo constructorInfo, int id, short type, string value) { 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; return result; } #pragma warning restore CA1416 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; } #pragma warning disable CA1416 internal static (string?, DateTime[], Shared.Models.Property) GetProperty(bool populateId, IMetadata? metadata, FileHolder fileHolder, Shared.Models.Property? property, bool isIgnoreExtension, bool isValidImageFormatExtension, int? id, ASCIIEncoding asciiEncoding) { Shared.Models.Property result; byte[] bytes; string value; long fileLength; 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; IReadOnlyList directories; DateTime? dateTimeFromName = GetDateTimeFromName(fileHolder); if (!isValidImageFormatExtension && fileHolder.Exists && metadata is not null) { try { directories = MetadataExtractor.ImageMetadataReader.ReadMetadata(fileHolder.FullName); (dateTimeOriginalByLogic, DateTime?[] metadataDateTimes) = metadata.GetDateTimes(fileHolder, directories); dateTimesByLogic = GetDateTimes(metadataDateTimes); message = null; } catch (Exception) { dateTimesByLogic = new(); message = string.Concat(new StackFrame().GetMethod()?.Name, " <", fileHolder.FullName, ">"); } } else if (!isIgnoreExtension && isValidImageFormatExtension && fileHolder.Exists) { try { using Image image = Image.FromFile(fileHolder.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 ??= GetDeterministicHashCode(bytes); } dateTimeFormat = IProperty.DateTimeFormat(); if (image.PropertyIdList.Contains((int)IExif.Tags.DateTime)) { propertyItem = image.GetPropertyItem((int)IExif.Tags.DateTime); 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((int)IExif.Tags.DateTimeDigitized)) { propertyItem = image.GetPropertyItem((int)IExif.Tags.DateTimeDigitized); 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((int)IExif.Tags.DateTimeOriginal)) { propertyItem = image.GetPropertyItem((int)IExif.Tags.DateTimeOriginal); 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((int)IExif.Tags.GPSDateStamp)) { propertyItem = image.GetPropertyItem((int)IExif.Tags.GPSDateStamp); 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((int)IExif.Tags.Make)) { propertyItem = image.GetPropertyItem((int)IExif.Tags.Make); if (propertyItem?.Value is not null) make = asciiEncoding.GetString(propertyItem.Value, 0, propertyItem.Len - 1); } if (image.PropertyIdList.Contains((int)IExif.Tags.Model)) { propertyItem = image.GetPropertyItem((int)IExif.Tags.Model); if (propertyItem?.Value is not null) model = asciiEncoding.GetString(propertyItem.Value, 0, propertyItem.Len - 1); } if (image.PropertyIdList.Contains((int)IExif.Tags.Orientation)) { propertyItem = image.GetPropertyItem((int)IExif.Tags.Orientation); if (propertyItem?.Value is not null) orientation = BitConverter.ToInt16(propertyItem.Value, 0); } if (image.PropertyIdList.Contains((int)IExif.Tags.XPKeywords)) { propertyItem = image.GetPropertyItem((int)IExif.Tags.XPKeywords); 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(';'); } } message = null; dateTimes = new DateTime?[] { fileHolder.LastWriteTime, fileHolder.CreationTime, dateTime, dateTimeDigitized, dateTimeOriginal, gpsDateStamp }; } catch (Exception) { dateTimes = Array.Empty(); message = string.Concat(new StackFrame().GetMethod()?.Name, " <", fileHolder.FullName, ">"); } if (metadata is not null && dateTimeOriginal is null) { try { directories = MetadataExtractor.ImageMetadataReader.ReadMetadata(fileHolder.FullName); (dateTimeOriginalByLogic, DateTime?[] metadataDateTimes) = metadata.GetDateTimes(fileHolder, directories); dateTimesByLogic = GetDateTimes(dateTimes, metadataDateTimes); message = null; } catch (Exception) { message = string.Concat(new StackFrame().GetMethod()?.Name, " <", fileHolder.FullName, ">"); } } if (dateTimeFromName is null) (dateTimeOriginalByLogic, dateTimesByLogic) = (dateTimeOriginal, GetDateTimes(fileHolder, dateTimes)); else (dateTimeOriginalByLogic, dateTimesByLogic) = (dateTimeOriginal, GetDateTimes(dateTimeFromName.Value, dateTimes)); } else (message, dateTimeOriginalByLogic, dateTimesByLogic) = (null, null, new()); if (fileHolder.Length is null) fileLength = 0; else fileLength = fileHolder.Length.Value; if (fileHolder.CreationTime is null && property?.CreationTime is null) throw new NullReferenceException(nameof(fileHolder.CreationTime)); if (fileHolder.LastWriteTime is null && property?.LastWriteTime is null) throw new NullReferenceException(nameof(fileHolder.LastWriteTime)); if (fileHolder.CreationTime is not null && fileHolder.LastWriteTime is not null) result = new(fileHolder.CreationTime.Value, dateTime, dateTimeDigitized, dateTimeFromName, dateTimeOriginalByLogic, fileLength, gpsDateStamp, height, id, keywords, fileHolder.LastWriteTime.Value, make, model, orientation?.ToString(), width); else if (property is not null) result = new(property.CreationTime, dateTime, dateTimeDigitized, dateTimeFromName, dateTimeOriginalByLogic, fileLength, gpsDateStamp, height, id, keywords, property.LastWriteTime, make, model, orientation?.ToString(), width); else throw new NullReferenceException(nameof(property)); return (message, dateTimesByLogic.ToArray(), result); } #pragma warning restore CA1416 internal static (DateTime?, DateTime[], int?, string?) Get(bool populateId, IMetadata? metadata, FileHolder fileHolder, 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, Array.Empty(), null); else (message, dateTimes, property) = GetProperty(populateId, metadata, fileHolder, property, isIgnoreExtension, isValidImageFormatExtension, id, asciiEncoding); return new(property?.DateTimeOriginal, dateTimes, property?.Id, message); } }