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 partial class Property { [GeneratedRegex(@"\D+")] private static partial Regex Digit(); 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; } private static List GetDateTimes(FileHolder fileHolder, DateTime?[] dateTimes) { List results = []; string[] digits = Digit().Split(fileHolder.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; } 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 = [ [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}.{fileHolder.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 (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 = []; foreach (DateTime? dateTime in metadataDateTimes) { if (dateTime is null || results.Contains(dateTime.Value)) continue; results.Add(dateTime.Value); } return results; } #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 = []; 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 ??= Shared.Models.Stateless.Methods.IId.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 = [fileHolder.LastWriteTime, fileHolder.CreationTime, dateTime, dateTimeDigitized, dateTimeOriginal, gpsDateStamp]; } catch (Exception) { dateTimes = []; 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, []); 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, [], null); else (message, dateTimes, property) = GetProperty(populateId, metadata, fileHolder, property, isIgnoreExtension, isValidImageFormatExtension, id, asciiEncoding); return new(property?.DateTimeOriginal, dateTimes, property?.Id, message); } }