398 lines
18 KiB
C#
398 lines
18 KiB
C#
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<DateTime> GetDateTimes(DateTime dateTimeFromName, DateTime?[] dateTimes)
|
|
{
|
|
List<DateTime> results = new() { dateTimeFromName };
|
|
foreach (DateTime? dateTime in dateTimes)
|
|
{
|
|
if (dateTime is null)
|
|
continue;
|
|
results.Add(dateTime.Value);
|
|
}
|
|
return results;
|
|
}
|
|
|
|
private static List<DateTime> GetDateTimes(DateTime?[] dateTimes, DateTime?[] metadataDateTimes)
|
|
{
|
|
List<DateTime> 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<DateTime> GetDateTimes(FileHolder fileHolder, DateTime?[] dateTimes)
|
|
{
|
|
List<DateTime> 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<DateTime> GetDateTimes(DateTime?[] metadataDateTimes)
|
|
{
|
|
List<DateTime> 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<MetadataExtractor.Directory>? 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<DateTime> dateTimesByLogic;
|
|
DateTime? dateTimeOriginal = null;
|
|
DateTime? dateTimeDigitized = null;
|
|
DateTime? dateTimeOriginalByLogic = null;
|
|
IReadOnlyList<MetadataExtractor.Directory> 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<DateTime?>();
|
|
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<MetadataExtractor.Directory>? 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<DateTime>(), null);
|
|
else
|
|
(message, dateTimes, property) = GetProperty(populateId, metadata, fileHolder, property, isIgnoreExtension, isValidImageFormatExtension, id, asciiEncoding);
|
|
return new(property?.DateTimeOriginal, dateTimes, property?.Id, message);
|
|
}
|
|
|
|
} |