From 80ca8f98eb1fca0ade79548eec40bb3049f2de88 Mon Sep 17 00:00:00 2001 From: Mike Phares Date: Sun, 20 Oct 2024 18:56:57 -0700 Subject: [PATCH] Exif Helper --- .vscode/launch.json | 1 + .vscode/settings.json | 2 + File-Folder-Helper.csproj | 1 + Helpers/Exif/Dimensions.cs | 126 +++++ Helpers/Exif/Exif.cs | 534 +++++++++++++++++++ Helpers/Exif/HelperExif.cs | 49 ++ Models/AppSettings.cs | 1 + Models/Binder/AppSettings.cs | 21 +- Models/Exif/AviDirectory.cs | 24 + Models/Exif/ExifDirectory.cs | 36 ++ Models/Exif/ExifDirectoryBase.cs | 66 +++ Models/Exif/FileMetadataDirectory.cs | 23 + Models/Exif/GifHeaderDirectory.cs | 22 + Models/Exif/GpsDirectory.cs | 26 + Models/Exif/JpegDirectory.cs | 22 + Models/Exif/MakernoteDirectory.cs | 23 + Models/Exif/PhotoshopDirectory.cs | 22 + Models/Exif/PngDirectory.cs | 23 + Models/Exif/QuickTimeMovieHeaderDirectory.cs | 21 + Models/Exif/QuickTimeTrackHeaderDirectory.cs | 21 + Models/Exif/WebPDirectory.cs | 22 + Models/Face/FaceEncoding.cs | 11 + Models/Face/FaceFile.cs | 33 ++ Models/Face/FacePart.cs | 54 ++ Models/Face/FacePoint.cs | 39 ++ Models/Face/Location.cs | 42 ++ Models/Face/MappingFromPerson.cs | 24 + Models/Face/OutputResolution.cs | 5 + Worker.cs | 3 + 29 files changed, 1287 insertions(+), 10 deletions(-) create mode 100644 Helpers/Exif/Dimensions.cs create mode 100644 Helpers/Exif/Exif.cs create mode 100644 Helpers/Exif/HelperExif.cs create mode 100644 Models/Exif/AviDirectory.cs create mode 100644 Models/Exif/ExifDirectory.cs create mode 100644 Models/Exif/ExifDirectoryBase.cs create mode 100644 Models/Exif/FileMetadataDirectory.cs create mode 100644 Models/Exif/GifHeaderDirectory.cs create mode 100644 Models/Exif/GpsDirectory.cs create mode 100644 Models/Exif/JpegDirectory.cs create mode 100644 Models/Exif/MakernoteDirectory.cs create mode 100644 Models/Exif/PhotoshopDirectory.cs create mode 100644 Models/Exif/PngDirectory.cs create mode 100644 Models/Exif/QuickTimeMovieHeaderDirectory.cs create mode 100644 Models/Exif/QuickTimeTrackHeaderDirectory.cs create mode 100644 Models/Exif/WebPDirectory.cs create mode 100644 Models/Face/FaceEncoding.cs create mode 100644 Models/Face/FaceFile.cs create mode 100644 Models/Face/FacePart.cs create mode 100644 Models/Face/FacePoint.cs create mode 100644 Models/Face/Location.cs create mode 100644 Models/Face/MappingFromPerson.cs create mode 100644 Models/Face/OutputResolution.cs diff --git a/.vscode/launch.json b/.vscode/launch.json index 971ec9c..fd3300b 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -11,6 +11,7 @@ "preLaunchTask": "build", "program": "${workspaceFolder}/bin/Debug/net8.0/win-x64/File-Folder-Helper.dll", "args": [ + "D:/5-Other-Small/Proxmox/exiftool/286628400329.jpg.jpg", "s", "X", "L:/DevOps/Mesa_FI/File-Folder-Helper/.vscode/helper/tfs", diff --git a/.vscode/settings.json b/.vscode/settings.json index 4c7725f..c41fe20 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -15,6 +15,7 @@ "CHIL", "DEAT", "endianness", + "Exif", "FAMC", "FAMS", "GIVN", @@ -29,6 +30,7 @@ "NSFX", "OBJE", "onenote", + "Permyriad", "pged", "Phares", "Reparse", diff --git a/File-Folder-Helper.csproj b/File-Folder-Helper.csproj index cc4ba9b..b496438 100644 --- a/File-Folder-Helper.csproj +++ b/File-Folder-Helper.csproj @@ -15,6 +15,7 @@ + diff --git a/Helpers/Exif/Dimensions.cs b/Helpers/Exif/Dimensions.cs new file mode 100644 index 0000000..ed7f524 --- /dev/null +++ b/Helpers/Exif/Dimensions.cs @@ -0,0 +1,126 @@ +using System.Drawing; + +namespace File_Folder_Helper.Helpers.Exif; + +internal static class Dimensions +{ + +#pragma warning disable IDE0230 + private static readonly Dictionary> _ImageFormatDecoders = new() + { + { new byte[] { 0x42, 0x4D }, DecodeBitmap }, + { new byte[] { 0x47, 0x49, 0x46, 0x38, 0x37, 0x61 }, DecodeGif }, + { new byte[] { 0x47, 0x49, 0x46, 0x38, 0x39, 0x61 }, DecodeGif }, + { new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A }, DecodePng }, + { new byte[] { 0xff, 0xd8 }, DecodeJfif }, + { new byte[] { 0x52, 0x49, 0x46, 0x46 }, DecodeWebP }, + }; +#pragma warning restore IDE0230 + + private static bool StartsWith(byte[] thisBytes, byte[] thatBytes) + { + for (int i = 0; i < thatBytes.Length; i += 1) + { + if (thisBytes[i] == thatBytes[i]) + continue; + return false; + } + return true; + } + + private static short ReadLittleEndianInt16(BinaryReader binaryReader) + { + byte[] bytes = new byte[sizeof(short)]; + for (int i = 0; i < sizeof(short); i += 1) + bytes[sizeof(short) - 1 - i] = binaryReader.ReadByte(); + return BitConverter.ToInt16(bytes, 0); + } + + private static int ReadLittleEndianInt32(BinaryReader binaryReader) + { + byte[] bytes = new byte[sizeof(int)]; + for (int i = 0; i < sizeof(int); i += 1) + bytes[sizeof(int) - 1 - i] = binaryReader.ReadByte(); + return BitConverter.ToInt32(bytes, 0); + } + + private static Size? DecodeBitmap(BinaryReader binaryReader) + { + _ = binaryReader.ReadBytes(16); + int width = binaryReader.ReadInt32(); + int height = binaryReader.ReadInt32(); + return new Size(width, height); + } + + private static Size? DecodeGif(BinaryReader binaryReader) + { + int width = binaryReader.ReadInt16(); + int height = binaryReader.ReadInt16(); + return new Size(width, height); + } + + private static Size? DecodePng(BinaryReader binaryReader) + { + _ = binaryReader.ReadBytes(8); + int width = ReadLittleEndianInt32(binaryReader); + int height = ReadLittleEndianInt32(binaryReader); + return new Size(width, height); + } + + private static Size? DecodeJfif(BinaryReader binaryReader) + { + while (binaryReader.ReadByte() == 0xff) + { + byte marker = binaryReader.ReadByte(); + short chunkLength = ReadLittleEndianInt16(binaryReader); + if (marker == 0xc0) + { + _ = binaryReader.ReadByte(); + int height = ReadLittleEndianInt16(binaryReader); + int width = ReadLittleEndianInt16(binaryReader); + return new Size(width, height); + } + if (chunkLength >= 0) + _ = binaryReader.ReadBytes(chunkLength - 2); + else + { + ushort uChunkLength = (ushort)chunkLength; + _ = binaryReader.ReadBytes(uChunkLength - 2); + } + } + return null; + } + + private static Size? DecodeWebP(BinaryReader binaryReader) + { + _ = binaryReader.ReadUInt32(); // Size + _ = binaryReader.ReadBytes(15); // WEBP, VP8 + more + _ = binaryReader.ReadBytes(3); // SYNC + int width = binaryReader.ReadUInt16() & 0b00_11111111111111; // 14 bits width + int height = binaryReader.ReadUInt16() & 0b00_11111111111111; // 14 bits height + return new Size(width, height); + } + + internal static Size? GetDimensions(BinaryReader binaryReader) + { + int maxMagicBytesLength = _ImageFormatDecoders.Keys.OrderByDescending(x => x.Length).First().Length; + byte[] magicBytes = new byte[maxMagicBytesLength]; + for (int i = 0; i < maxMagicBytesLength; i += 1) + { + magicBytes[i] = binaryReader.ReadByte(); + foreach (KeyValuePair> kvPair in _ImageFormatDecoders) + { + if (StartsWith(magicBytes, kvPair.Key)) + return kvPair.Value(binaryReader); + } + } + return null; + } + + internal static Size? GetDimensions(string path) + { + using BinaryReader binaryReader = new(File.OpenRead(path)); + return GetDimensions(binaryReader); + } + +} \ No newline at end of file diff --git a/Helpers/Exif/Exif.cs b/Helpers/Exif/Exif.cs new file mode 100644 index 0000000..f19c835 --- /dev/null +++ b/Helpers/Exif/Exif.cs @@ -0,0 +1,534 @@ +using MetadataExtractor; +using MetadataExtractor.Formats.Exif; +using MetadataExtractor.Formats.Exif.Makernotes; +using System.Globalization; + +namespace File_Folder_Helper.Helpers.Exif; + +internal abstract class Exif +{ + + private static DateTime? GetDateTime(string? value) + { + DateTime? result; + string dateTimeFormat = "yyyy:MM:dd HH:mm:ss"; + 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; + } + + private static Models.Exif.AviDirectory[] GetAviDirectories(IReadOnlyList directories) + { + List results = []; + IEnumerable aviDirectories = directories.OfType(); + foreach (MetadataExtractor.Formats.Avi.AviDirectory aviDirectory in aviDirectories) + { + if (aviDirectory.Tags.Count == 0) + continue; + DateTime? dateTimeOriginal; + string? duration = aviDirectory.GetDescription(MetadataExtractor.Formats.Avi.AviDirectory.TagDuration); + string? height = aviDirectory.GetDescription(MetadataExtractor.Formats.Avi.AviDirectory.TagHeight); + string? width = aviDirectory.GetDescription(MetadataExtractor.Formats.Avi.AviDirectory.TagWidth); + if (aviDirectory.TryGetDateTime(MetadataExtractor.Formats.Avi.AviDirectory.TagDateTimeOriginal, out DateTime checkDateTime)) + dateTimeOriginal = checkDateTime; + else + dateTimeOriginal = GetDateTime(aviDirectory.GetString(MetadataExtractor.Formats.Avi.AviDirectory.TagDateTimeOriginal)); + if (dateTimeOriginal is null && duration is null && height is null && width is null) + continue; + results.Add(new(dateTimeOriginal, duration, height, width)); + } + return results.ToArray(); + } + + private static Models.Exif.ExifDirectoryBase[] GetExifBaseDirectories(IReadOnlyList directories) + { + List results = []; + IEnumerable exifBaseDirectories = directories.OfType(); + foreach (ExifDirectoryBase exifDirectoryBase in exifBaseDirectories) + { + if (exifDirectoryBase.Tags.Count == 0) + continue; + DateTime? dateTime; + DateTime checkDateTime; + DateTime? dateTimeOriginal; + DateTime? dateTimeDigitized; + string? aperture = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagAperture); + string? applicationNotes = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagApplicationNotes); + string? artist = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagArtist); + string? bitsPerSample = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagBitsPerSample); + string? bodySerialNumber = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagBodySerialNumber); + string? cameraOwnerName = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagCameraOwnerName); + string? compressedAverageBitsPerPixel = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagCompressedAverageBitsPerPixel); + string? compression = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagCompression); + string? copyright = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagCopyright); + string? documentName = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagDocumentName); + string? exifVersion = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagExifVersion); + string? exposureTime = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagExposureTime); + string? fileSource = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagFileSource); + string? imageDescription = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagImageDescription); + string? imageHeight = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagImageHeight); + string? imageNumber = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagImageNumber); + string? imageUniqueId = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagImageUniqueId); + string? imageWidth = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagImageWidth); + string? isoSpeed = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagIsoSpeed); + string? lensMake = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagLensMake); + string? lensModel = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagLensModel); + string? lensSerialNumber = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagLensSerialNumber); + string? make = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagMake); + string? makerNote = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagMakernote); + string? model = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagModel); + string? orientation = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagOrientation); + int? orientationValue = orientation is null ? null : exifDirectoryBase.GetInt32(ExifDirectoryBase.TagOrientation); + string? rating = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagRating); + string? ratingPercent = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagRatingPercent); + string? securityClassification = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagSecurityClassification); + string? shutterSpeed = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagShutterSpeed); + string? software = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagSoftware); + string? timeZone = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagTimeZone); + string? timeZoneDigitized = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagTimeZoneDigitized); + string? timeZoneOriginal = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagTimeZoneOriginal); + string? userComment = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagUserComment); + string? winAuthor = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagWinAuthor); + string? winComment = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagWinComment); + string? winKeywords = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagWinKeywords); + string? winSubject = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagWinSubject); + string? winTitle = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagWinTitle); + string? xResolution = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagXResolution); + string? yResolution = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagYResolution); + if (exifDirectoryBase.TryGetDateTime(ExifDirectoryBase.TagDateTime, out checkDateTime)) + dateTime = checkDateTime; + else + dateTime = GetDateTime(exifDirectoryBase.GetString(ExifDirectoryBase.TagDateTime)); + if (exifDirectoryBase.TryGetDateTime(ExifDirectoryBase.TagDateTimeOriginal, out checkDateTime)) + dateTimeOriginal = checkDateTime; + else + dateTimeOriginal = GetDateTime(exifDirectoryBase.GetString(ExifDirectoryBase.TagDateTimeOriginal)); + if (exifDirectoryBase.TryGetDateTime(ExifDirectoryBase.TagDateTimeDigitized, out checkDateTime)) + dateTimeDigitized = checkDateTime; + else + dateTimeDigitized = GetDateTime(exifDirectoryBase.GetString(ExifDirectoryBase.TagDateTimeDigitized)); + if (userComment is not null && userComment.Length > 255) + userComment = "..."; + if (aperture is null + && applicationNotes is null + && artist is null + && bitsPerSample is null + && bodySerialNumber is null + && cameraOwnerName is null + && compressedAverageBitsPerPixel is null + && compression is null + && copyright is null + && dateTime is null + && dateTimeDigitized is null + && dateTimeOriginal is null + && documentName is null + && exifVersion is null + && exposureTime is null + && fileSource is null + && imageDescription is null + && imageHeight is null + && imageNumber is null + && imageUniqueId is null + && imageWidth is null + && isoSpeed is null + && lensMake is null + && lensModel is null + && lensSerialNumber is null + && make is null + && makerNote is null + && model is null + && orientation is null + && orientationValue is null + && rating is null + && ratingPercent is null + && securityClassification is null + && shutterSpeed is null + && software is null + && timeZone is null + && timeZoneDigitized is null + && timeZoneOriginal is null + && userComment is null + && winAuthor is null + && winComment is null + && winKeywords is null + && winSubject is null + && winTitle is null + && xResolution is not null + && yResolution is null) + continue; + results.Add(new(aperture, + applicationNotes, + artist, + bitsPerSample, + bodySerialNumber, + cameraOwnerName, + compressedAverageBitsPerPixel, + compression, + copyright, + dateTime, + dateTimeDigitized, + dateTimeOriginal, + documentName, + exifVersion, + exposureTime, + fileSource, + imageDescription, + imageHeight, + imageNumber, + imageUniqueId, + imageWidth, + isoSpeed, + lensMake, + lensModel, + lensSerialNumber, + make, + makerNote, + model, + orientation, + orientationValue, + rating, + ratingPercent, + securityClassification, + shutterSpeed, + software, + timeZone, + timeZoneDigitized, + timeZoneOriginal, + userComment, + winAuthor, + winComment, + winKeywords, + winSubject, + winTitle, + xResolution, + yResolution)); + } + return results.ToArray(); + } + + private static Models.Exif.FileMetadataDirectory[] GetFileMetadataDirectories(string file, IReadOnlyList directories) + { + List results = []; + IEnumerable fileMetadataDirectories = directories.OfType(); + foreach (MetadataExtractor.Formats.FileSystem.FileMetadataDirectory fileMetadataDirectory in fileMetadataDirectories) + { + if (fileMetadataDirectory.Tags.Count == 0) + continue; + DateTime? fileModifiedDate; + string? fileName = fileMetadataDirectory.GetDescription(MetadataExtractor.Formats.FileSystem.FileMetadataDirectory.TagFileName); + string? fileSize = fileMetadataDirectory.GetDescription(MetadataExtractor.Formats.FileSystem.FileMetadataDirectory.TagFileSize); + if (fileMetadataDirectory.TryGetDateTime(MetadataExtractor.Formats.FileSystem.FileMetadataDirectory.TagFileModifiedDate, out DateTime checkDateTime)) + fileModifiedDate = checkDateTime; + else + fileModifiedDate = GetDateTime(fileMetadataDirectory.GetString(MetadataExtractor.Formats.FileSystem.FileMetadataDirectory.TagFileModifiedDate)); + if (fileName is null || !file.EndsWith(fileName)) + throw new NotSupportedException($"!{file}.EndsWith({fileName})"); + if (fileModifiedDate is null && fileName is null && fileSize is null) + continue; + results.Add(new(fileModifiedDate, fileName, fileSize)); + } + return results.ToArray(); + } + + private static Models.Exif.GifHeaderDirectory[] GetGifHeaderDirectories(IReadOnlyList directories) + { + List results = []; + IEnumerable gifHeaderDirectories = directories.OfType(); + foreach (MetadataExtractor.Formats.Gif.GifHeaderDirectory gifHeaderDirectory in gifHeaderDirectories) + { + if (gifHeaderDirectory.Tags.Count == 0) + continue; + string? imageHeight = gifHeaderDirectory.GetDescription(MetadataExtractor.Formats.Gif.GifHeaderDirectory.TagImageHeight); + string? imageWidth = gifHeaderDirectory.GetDescription(MetadataExtractor.Formats.Gif.GifHeaderDirectory.TagImageWidth); + if (imageHeight is null && imageWidth is null) + continue; + results.Add(new(imageHeight, imageWidth)); + } + return results.ToArray(); + } + + private static Models.Exif.GpsDirectory[] GetGpsDirectories(IReadOnlyList directories) + { + List results = []; + IEnumerable gpsDirectories = directories.OfType(); + foreach (GpsDirectory gpsDirectory in gpsDirectories) + { + if (gpsDirectory.Tags.Count == 0) + continue; + DateTime? timeStamp; + string? altitude = gpsDirectory.GetDescription(GpsDirectory.TagAltitude); + string? latitude = gpsDirectory.GetDescription(GpsDirectory.TagLatitude); + string? latitudeRef = gpsDirectory.GetDescription(GpsDirectory.TagLatitudeRef); + string? longitude = gpsDirectory.GetDescription(GpsDirectory.TagLongitude); + string? longitudeRef = gpsDirectory.GetDescription(GpsDirectory.TagLongitudeRef); + if (gpsDirectory.TryGetDateTime(GpsDirectory.TagTimeStamp, out DateTime checkDateTime)) + timeStamp = checkDateTime; + else + timeStamp = GetDateTime(gpsDirectory.GetString(GpsDirectory.TagTimeStamp)); + if (altitude is null && latitude is null && latitudeRef is null && longitude is null && longitudeRef is null && timeStamp is null) + continue; + results.Add(new(altitude, + latitude, + latitudeRef, + longitude, + longitudeRef, + timeStamp)); + } + return results.ToArray(); + } + + private static Models.Exif.JpegDirectory[] GetJpegDirectories(IReadOnlyList directories) + { + List results = []; + IEnumerable jpegDirectories = directories.OfType(); + foreach (MetadataExtractor.Formats.Jpeg.JpegDirectory jpegDirectory in jpegDirectories) + { + if (jpegDirectory.Tags.Count == 0) + continue; + string? imageHeight = jpegDirectory.GetDescription(MetadataExtractor.Formats.Jpeg.JpegDirectory.TagImageHeight); + string? imageWidth = jpegDirectory.GetDescription(MetadataExtractor.Formats.Jpeg.JpegDirectory.TagImageWidth); + if (imageHeight is null && imageWidth is null) + continue; + results.Add(new(imageHeight, imageWidth)); + } + return results.ToArray(); + } + + private static Models.Exif.MakernoteDirectory[] GetMakernoteDirectories(IReadOnlyList directories) + { + List results = []; + IEnumerable appleMakernoteDirectories = directories.OfType(); + foreach (AppleMakernoteDirectory appleMakernoteDirectory in appleMakernoteDirectories) + { + if (appleMakernoteDirectory.Tags.Count == 0) + continue; + string? cameraSerialNumber = null; + string? firmwareVersion = null; + string? qualityAndFileFormat = null; + if (cameraSerialNumber is null && firmwareVersion is null && qualityAndFileFormat is null) + continue; + results.Add(new(cameraSerialNumber, firmwareVersion, qualityAndFileFormat)); + } + IEnumerable canonMakernoteDirectories = directories.OfType(); + foreach (CanonMakernoteDirectory canonMakernoteDirectory in canonMakernoteDirectories) + { + if (canonMakernoteDirectory.Tags.Count == 0) + continue; + string? cameraSerialNumber = canonMakernoteDirectory.GetDescription(CanonMakernoteDirectory.TagModelId); + string? firmwareVersion = canonMakernoteDirectory.GetDescription(CanonMakernoteDirectory.TagCanonFirmwareVersion); + string? qualityAndFileFormat = canonMakernoteDirectory.GetDescription(CanonMakernoteDirectory.CameraSettings.TagQuality); + if (cameraSerialNumber is null && firmwareVersion is null && qualityAndFileFormat is null) + continue; + results.Add(new(cameraSerialNumber, firmwareVersion, qualityAndFileFormat)); + } + IEnumerable nikonType2MakernoteDirectories = directories.OfType(); + foreach (NikonType2MakernoteDirectory nikonType2MakernoteDirectory in nikonType2MakernoteDirectories) + { + if (nikonType2MakernoteDirectory.Tags.Count == 0) + continue; + string? cameraSerialNumber = nikonType2MakernoteDirectory.GetDescription(NikonType2MakernoteDirectory.TagCameraSerialNumber); + string? firmwareVersion = nikonType2MakernoteDirectory.GetDescription(NikonType2MakernoteDirectory.TagFirmwareVersion); + string? qualityAndFileFormat = nikonType2MakernoteDirectory.GetDescription(NikonType2MakernoteDirectory.TagQualityAndFileFormat); + if (cameraSerialNumber is null && firmwareVersion is null && qualityAndFileFormat is null) + continue; + results.Add(new(cameraSerialNumber, firmwareVersion, qualityAndFileFormat)); + } + IEnumerable olympusMakernoteDirectories = directories.OfType(); + foreach (OlympusMakernoteDirectory olympusMakernoteDirectory in olympusMakernoteDirectories) + { + if (olympusMakernoteDirectory.Tags.Count == 0) + continue; + string? cameraSerialNumber = olympusMakernoteDirectory.GetDescription(OlympusMakernoteDirectory.TagSerialNumber1); + string? firmwareVersion = olympusMakernoteDirectory.GetDescription(OlympusMakernoteDirectory.TagBodyFirmwareVersion); + string? qualityAndFileFormat = olympusMakernoteDirectory.GetDescription(OlympusMakernoteDirectory.TagJpegQuality); + if (cameraSerialNumber is null && firmwareVersion is null && qualityAndFileFormat is null) + continue; + results.Add(new(cameraSerialNumber, firmwareVersion, qualityAndFileFormat)); + } + IEnumerable panasonicMakernoteDirectories = directories.OfType(); + foreach (PanasonicMakernoteDirectory panasonicMakernoteDirectory in panasonicMakernoteDirectories) + { + if (panasonicMakernoteDirectory.Tags.Count == 0) + continue; + string? cameraSerialNumber = panasonicMakernoteDirectory.GetDescription(PanasonicMakernoteDirectory.TagInternalSerialNumber); + string? firmwareVersion = panasonicMakernoteDirectory.GetDescription(PanasonicMakernoteDirectory.TagFirmwareVersion); + string? qualityAndFileFormat = panasonicMakernoteDirectory.GetDescription(PanasonicMakernoteDirectory.TagQualityMode); + if (cameraSerialNumber is null && firmwareVersion is null && qualityAndFileFormat is null) + continue; + results.Add(new(cameraSerialNumber, firmwareVersion, qualityAndFileFormat)); + } + IEnumerable samsungType2MakernoteDirectories = directories.OfType(); + foreach (SamsungType2MakernoteDirectory samsungType2MakernoteDirectory in samsungType2MakernoteDirectories) + { + if (samsungType2MakernoteDirectory.Tags.Count == 0) + continue; + string? cameraSerialNumber = samsungType2MakernoteDirectory.GetDescription(SamsungType2MakernoteDirectory.TagSerialNumber); + string? firmwareVersion = samsungType2MakernoteDirectory.GetDescription(SamsungType2MakernoteDirectory.TagFirmwareName); + string? qualityAndFileFormat = null; + if (cameraSerialNumber is null && firmwareVersion is null && qualityAndFileFormat is null) + continue; + results.Add(new(cameraSerialNumber, firmwareVersion, qualityAndFileFormat)); + } + IEnumerable sonyType6MakernoteDirectories = directories.OfType(); + foreach (SonyType6MakernoteDirectory sonyType6MakernoteDirectory in sonyType6MakernoteDirectories) + { + if (sonyType6MakernoteDirectory.Tags.Count == 0) + continue; + string? cameraSerialNumber = null; + string? firmwareVersion = null; + string? qualityAndFileFormat = null; + if (cameraSerialNumber is null && firmwareVersion is null && qualityAndFileFormat is null) + continue; + results.Add(new(cameraSerialNumber, firmwareVersion, qualityAndFileFormat)); + } + return results.ToArray(); + } + + private static Models.Exif.PhotoshopDirectory[] GetPhotoshopDirectories(IReadOnlyList directories) + { + List results = []; + IEnumerable photoshopDirectories = directories.OfType(); + foreach (MetadataExtractor.Formats.Photoshop.PhotoshopDirectory photoshopDirectory in photoshopDirectories) + { + if (photoshopDirectory.Tags.Count == 0) + continue; + string? jpegQuality = photoshopDirectory.GetDescription(MetadataExtractor.Formats.Photoshop.PhotoshopDirectory.TagJpegQuality); + string? url = photoshopDirectory.GetDescription(MetadataExtractor.Formats.Photoshop.PhotoshopDirectory.TagUrl); + if (jpegQuality is null && url is null) + continue; + results.Add(new(jpegQuality, url)); + } + return results.ToArray(); + } + + private static Models.Exif.PngDirectory[] GetPngDirectories(IReadOnlyList directories) + { + List results = []; + IEnumerable pngDirectories = directories.OfType(); + foreach (MetadataExtractor.Formats.Png.PngDirectory pngDirectory in pngDirectories) + { + if (pngDirectory.Tags.Count == 0) + continue; + string? imageHeight = pngDirectory.GetDescription(MetadataExtractor.Formats.Png.PngDirectory.TagImageHeight); + string? imageWidth = pngDirectory.GetDescription(MetadataExtractor.Formats.Png.PngDirectory.TagImageWidth); + string? textualData = pngDirectory.GetDescription(MetadataExtractor.Formats.Png.PngDirectory.TagTextualData); + if (imageHeight is null && imageWidth is null && textualData is null) + continue; + results.Add(new(imageHeight, imageWidth, textualData)); + } + return results.ToArray(); + } + + private static Models.Exif.QuickTimeMovieHeaderDirectory[] GetQuickTimeMovieHeaderDirectoryDirectories(IReadOnlyList directories) + { + List results = []; + IEnumerable quickTimeMovieHeaderDirectories = directories.OfType(); + foreach (MetadataExtractor.Formats.QuickTime.QuickTimeMovieHeaderDirectory quickTimeMovieHeaderDirectory in quickTimeMovieHeaderDirectories) + { + if (quickTimeMovieHeaderDirectory.Tags.Count == 0) + continue; + DateTime? created; + if (quickTimeMovieHeaderDirectory.TryGetDateTime(MetadataExtractor.Formats.QuickTime.QuickTimeMovieHeaderDirectory.TagCreated, out DateTime checkDateTime)) + created = checkDateTime; + else + created = GetDateTime(quickTimeMovieHeaderDirectory.GetString(MetadataExtractor.Formats.QuickTime.QuickTimeMovieHeaderDirectory.TagCreated)); + if (created is null) + continue; + results.Add(new(created)); + } + return results.ToArray(); + } + + private static Models.Exif.QuickTimeTrackHeaderDirectory[] GetQuickTimeTrackHeaderDirectoryDirectories(IReadOnlyList directories) + { + List results = []; + IEnumerable quickTimeTrackHeaderDirectories = directories.OfType(); + foreach (MetadataExtractor.Formats.QuickTime.QuickTimeTrackHeaderDirectory quickTimeTrackHeaderDirectory in quickTimeTrackHeaderDirectories) + { + if (quickTimeTrackHeaderDirectory.Tags.Count == 0) + continue; + DateTime? created; + if (quickTimeTrackHeaderDirectory.TryGetDateTime(MetadataExtractor.Formats.QuickTime.QuickTimeTrackHeaderDirectory.TagCreated, out DateTime checkDateTime)) + created = checkDateTime; + else + created = GetDateTime(quickTimeTrackHeaderDirectory.GetString(MetadataExtractor.Formats.QuickTime.QuickTimeTrackHeaderDirectory.TagCreated)); + if (created is null) + continue; + results.Add(new(created)); + } + return results.ToArray(); + } + + private static Models.Exif.WebPDirectory[] GetWebPDirectories(IReadOnlyList directories) + { + List results = []; + IEnumerable webPDirectories = directories.OfType(); + foreach (MetadataExtractor.Formats.WebP.WebPDirectory webPDirectory in webPDirectories) + { + if (webPDirectory.Tags.Count == 0) + continue; + string? imageHeight = webPDirectory.GetDescription(MetadataExtractor.Formats.WebP.WebPDirectory.TagImageHeight); + string? imageWidth = webPDirectory.GetDescription(MetadataExtractor.Formats.WebP.WebPDirectory.TagImageWidth); + if (imageHeight is null && imageWidth is null) + continue; + results.Add(new(imageHeight, imageWidth)); + } + return results.ToArray(); + } + + private static Models.Exif.ExifDirectory Covert(FileInfo fileInfo, System.Drawing.Size? size, int id, IReadOnlyList directories) + { + Models.Exif.ExifDirectory result; + Models.Exif.AviDirectory[] aviDirectories = GetAviDirectories(directories); + Models.Exif.GpsDirectory[] gpsDirectories = GetGpsDirectories(directories); + Models.Exif.PngDirectory[] pngDirectories = GetPngDirectories(directories); + Models.Exif.JpegDirectory[] jpegDirectories = GetJpegDirectories(directories); + Models.Exif.WebPDirectory[] webPDirectories = GetWebPDirectories(directories); + Models.Exif.ExifDirectoryBase[] exifBaseDirectories = GetExifBaseDirectories(directories); + Models.Exif.GifHeaderDirectory[] gifHeaderDirectories = GetGifHeaderDirectories(directories); + Models.Exif.MakernoteDirectory[] MakernoteDirectories = GetMakernoteDirectories(directories); + Models.Exif.PhotoshopDirectory[] photoshopDirectories = GetPhotoshopDirectories(directories); + Models.Exif.FileMetadataDirectory[] fileMetadataDirectories = GetFileMetadataDirectories(fileInfo.FullName, directories); + Models.Exif.QuickTimeMovieHeaderDirectory[] quickTimeMovieHeaderDirectories = GetQuickTimeMovieHeaderDirectoryDirectories(directories); + Models.Exif.QuickTimeTrackHeaderDirectory[] quickTimeTrackHeaderDirectories = GetQuickTimeTrackHeaderDirectoryDirectories(directories); + result = new(aviDirectories, + exifBaseDirectories, + fileMetadataDirectories, + gifHeaderDirectories, + gpsDirectories, + size?.Height, + id, + jpegDirectories, + MakernoteDirectories, + fileInfo.Name, + photoshopDirectories, + pngDirectories, + quickTimeMovieHeaderDirectories, + quickTimeTrackHeaderDirectories, + webPDirectories, + size?.Width); + return result; + } + + internal static Models.Exif.ExifDirectory GetExifDirectory(FileInfo fileInfo) + { + Models.Exif.ExifDirectory result; + int id = 1; + System.Drawing.Size? size; + try + { size = Dimensions.GetDimensions(fileInfo.FullName); } + catch (Exception) + { size = null; } + IReadOnlyList directories = ImageMetadataReader.ReadMetadata(fileInfo.FullName); + result = Covert(fileInfo, size, id, directories); + return result; + } + +} \ No newline at end of file diff --git a/Helpers/Exif/HelperExif.cs b/Helpers/Exif/HelperExif.cs new file mode 100644 index 0000000..671728e --- /dev/null +++ b/Helpers/Exif/HelperExif.cs @@ -0,0 +1,49 @@ +using File_Folder_Helper.Models.Exif; +using File_Folder_Helper.Models.Face; +using Microsoft.Extensions.Logging; +using System.Collections.ObjectModel; +using System.Text.Json; + +namespace File_Folder_Helper.Helpers.Exif; + +internal static class HelperExif +{ + + private static ReadOnlyCollection GetCollection(ExifDirectoryBase[]? exifDirectoryBases) + { + List results = []; + if (exifDirectoryBases is not null) + { + string? json; + FaceFile[]? collection; + foreach (ExifDirectoryBase exifDirectoryBase in exifDirectoryBases) + { + json = exifDirectoryBase.Artist; + if (string.IsNullOrEmpty(json)) + continue; + collection = JsonSerializer.Deserialize(json, FaceFileCollectionGenerationContext.Default.FaceFileArray); + if (collection is null) + continue; + results.AddRange(collection); + } + } + return new(results); + } + + internal static void DragAndDrop(ILogger logger, string argZero) + { + FileInfo fileInfo = new(argZero); + logger.LogInformation("<{argZero}> exists", argZero); + ExifDirectory exifDirectory = Exif.GetExifDirectory(fileInfo); + ReadOnlyCollection collection = GetCollection(exifDirectory.ExifBaseDirectories); + logger.LogInformation("<{collection}> value", collection.Count); + foreach (FaceFile faceFile in collection) + { + if (faceFile.MappingFromPerson is null) + logger.LogInformation("<{Confidence}> value", faceFile.Location?.Confidence); + else + logger.LogInformation("<{DisplayDirectoryName}> value", faceFile.MappingFromPerson.DisplayDirectoryName); + } + } + +} \ No newline at end of file diff --git a/Models/AppSettings.cs b/Models/AppSettings.cs index d948ce2..422b977 100644 --- a/Models/AppSettings.cs +++ b/Models/AppSettings.cs @@ -9,6 +9,7 @@ public record AppSettings(string Company, string[] ExcludeSchemes, string PersonBirthdayFormat, char[] PersonTitleFilters, + string[] ValidImageFormatExtensions, string WorkingDirectoryName) { diff --git a/Models/Binder/AppSettings.cs b/Models/Binder/AppSettings.cs index 4fe31a2..c69bde5 100644 --- a/Models/Binder/AppSettings.cs +++ b/Models/Binder/AppSettings.cs @@ -13,6 +13,7 @@ public class AppSettings public string[]? ExcludeSchemes { get; set; } public string? PersonBirthdayFormat { get; set; } public string? PersonTitleFilters { get; set; } + public string[]? ValidImageFormatExtensions { get; set; } public string? WorkingDirectoryName { get; set; } public override string ToString() @@ -47,20 +48,20 @@ public class AppSettings if (appSettings?.ExcludeSchemes is null) throw new NullReferenceException(nameof(appSettings.ExcludeSchemes)); if (appSettings?.PersonBirthdayFormat is null) throw new NullReferenceException(nameof(appSettings.PersonBirthdayFormat)); if (appSettings?.PersonTitleFilters is null) throw new NullReferenceException(nameof(appSettings.PersonTitleFilters)); + if (appSettings?.ValidImageFormatExtensions is null) throw new NullReferenceException(nameof(appSettings.ValidImageFormatExtensions)); if (appSettings?.WorkingDirectoryName is null) throw new NullReferenceException(nameof(appSettings.WorkingDirectoryName)); - result = new( - appSettings.Company, - appSettings.DefaultNoteType, - appSettings.ExcludeDirectoryNames, - appSettings.ExcludeSchemes, - appSettings.PersonBirthdayFormat, - appSettings.PersonTitleFilters.ToArray(), - appSettings.WorkingDirectoryName - ); + result = new(appSettings.Company, + appSettings.DefaultNoteType, + appSettings.ExcludeDirectoryNames, + appSettings.ExcludeSchemes, + appSettings.PersonBirthdayFormat, + appSettings.PersonTitleFilters.ToArray(), + appSettings.ValidImageFormatExtensions, + appSettings.WorkingDirectoryName); return result; } - internal static Models.AppSettings Get(IConfigurationRoot configurationRoot) + public static Models.AppSettings Get(IConfigurationRoot configurationRoot) { Models.AppSettings result; #pragma warning disable IL3050, IL2026 diff --git a/Models/Exif/AviDirectory.cs b/Models/Exif/AviDirectory.cs new file mode 100644 index 0000000..a275b9f --- /dev/null +++ b/Models/Exif/AviDirectory.cs @@ -0,0 +1,24 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace File_Folder_Helper.Models.Exif; + +public record AviDirectory(DateTime? DateTimeOriginal, + string? Duration, + string? Height, + string? Width) +{ + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, AviDirectorySourceGenerationContext.Default.AviDirectory); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(AviDirectory))] +public partial class AviDirectorySourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Models/Exif/ExifDirectory.cs b/Models/Exif/ExifDirectory.cs new file mode 100644 index 0000000..0e678ed --- /dev/null +++ b/Models/Exif/ExifDirectory.cs @@ -0,0 +1,36 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace File_Folder_Helper.Models.Exif; + +public record ExifDirectory(AviDirectory[] AviDirectories, + ExifDirectoryBase[] ExifBaseDirectories, + FileMetadataDirectory[] FileMetadataDirectories, + GifHeaderDirectory[] GifHeaderDirectories, + GpsDirectory[] GpsDirectories, + int? Height, + int? Id, + JpegDirectory[] JpegDirectories, + MakernoteDirectory[] MakernoteDirectories, + string OriginalFileName, + PhotoshopDirectory[] PhotoshopDirectories, + PngDirectory[] PngDirectories, + QuickTimeMovieHeaderDirectory[] QuickTimeMovieHeaderDirectories, + QuickTimeTrackHeaderDirectory[] QuickTimeTrackHeaderDirectories, + WebPDirectory[] WebPDirectories, + int? Width) +{ + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, ExifDirectorySourceGenerationContext.Default.ExifDirectory); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] +[JsonSerializable(typeof(ExifDirectory))] +public partial class ExifDirectorySourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Models/Exif/ExifDirectoryBase.cs b/Models/Exif/ExifDirectoryBase.cs new file mode 100644 index 0000000..935291a --- /dev/null +++ b/Models/Exif/ExifDirectoryBase.cs @@ -0,0 +1,66 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace File_Folder_Helper.Models.Exif; + +public record ExifDirectoryBase(string? Aperture, + string? ApplicationNotes, + string? Artist, + string? BitsPerSample, + string? BodySerialNumber, + string? CameraOwnerName, + string? CompressedAverageBitsPerPixel, + string? Compression, + string? Copyright, + DateTime? DateTime, + DateTime? DateTimeDigitized, + DateTime? DateTimeOriginal, + string? DocumentName, + string? ExifVersion, + string? ExposureTime, + string? FileSource, + string? ImageDescription, + string? ImageHeight, + string? ImageNumber, + string? ImageUniqueId, + string? ImageWidth, + string? IsoSpeed, + string? LensMake, + string? LensModel, + string? LensSerialNumber, + string? Make, + string? MakerNote, + string? Model, + string? Orientation, + int? OrientationValue, + string? Rating, + string? RatingPercent, + string? SecurityClassification, + string? ShutterSpeed, + string? Software, + string? TimeZone, + string? TimeZoneDigitized, + string? TimeZoneOriginal, + string? UserComment, + string? WinAuthor, + string? WinComment, + string? WinKeywords, + string? WinSubject, + string? WinTitle, + string? XResolution, + string? YResolution) +{ + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, ExifDirectoryBaseSourceGenerationContext.Default.ExifDirectoryBase); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(ExifDirectoryBase))] +public partial class ExifDirectoryBaseSourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Models/Exif/FileMetadataDirectory.cs b/Models/Exif/FileMetadataDirectory.cs new file mode 100644 index 0000000..c971390 --- /dev/null +++ b/Models/Exif/FileMetadataDirectory.cs @@ -0,0 +1,23 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace File_Folder_Helper.Models.Exif; + +public record FileMetadataDirectory(DateTime? FileModifiedDate, + string? FileName, + string? FileSize) +{ + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, FileMetadataDirectorySourceGenerationContext.Default.FileMetadataDirectory); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(FileMetadataDirectory))] +public partial class FileMetadataDirectorySourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Models/Exif/GifHeaderDirectory.cs b/Models/Exif/GifHeaderDirectory.cs new file mode 100644 index 0000000..b8fc7b8 --- /dev/null +++ b/Models/Exif/GifHeaderDirectory.cs @@ -0,0 +1,22 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace File_Folder_Helper.Models.Exif; + +public record GifHeaderDirectory(string? ImageHeight, + string? ImageWidth) +{ + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, GifHeaderDirectorySourceGenerationContext.Default.GifHeaderDirectory); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(GifHeaderDirectory))] +public partial class GifHeaderDirectorySourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Models/Exif/GpsDirectory.cs b/Models/Exif/GpsDirectory.cs new file mode 100644 index 0000000..02bf8b2 --- /dev/null +++ b/Models/Exif/GpsDirectory.cs @@ -0,0 +1,26 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace File_Folder_Helper.Models.Exif; + +public record GpsDirectory(string? Altitude, + string? Latitude, + string? LatitudeRef, + string? Longitude, + string? LongitudeRef, + DateTime? TimeStamp) +{ + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, GpsDirectorySourceGenerationContext.Default.GpsDirectory); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(GpsDirectory))] +public partial class GpsDirectorySourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Models/Exif/JpegDirectory.cs b/Models/Exif/JpegDirectory.cs new file mode 100644 index 0000000..43d8a14 --- /dev/null +++ b/Models/Exif/JpegDirectory.cs @@ -0,0 +1,22 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace File_Folder_Helper.Models.Exif; + +public record JpegDirectory(string? ImageHeight, + string? ImageWidth) +{ + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, JpegDirectorySourceGenerationContext.Default.JpegDirectory); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(JpegDirectory))] +public partial class JpegDirectorySourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Models/Exif/MakernoteDirectory.cs b/Models/Exif/MakernoteDirectory.cs new file mode 100644 index 0000000..7304b10 --- /dev/null +++ b/Models/Exif/MakernoteDirectory.cs @@ -0,0 +1,23 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace File_Folder_Helper.Models.Exif; + +public record MakernoteDirectory(string? CameraSerialNumber, + string? FirmwareVersion, + string? QualityAndFileFormat) +{ + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, MakernoteDirectorySourceGenerationContext.Default.MakernoteDirectory); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(MakernoteDirectory))] +public partial class MakernoteDirectorySourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Models/Exif/PhotoshopDirectory.cs b/Models/Exif/PhotoshopDirectory.cs new file mode 100644 index 0000000..37c68e7 --- /dev/null +++ b/Models/Exif/PhotoshopDirectory.cs @@ -0,0 +1,22 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace File_Folder_Helper.Models.Exif; + +public record PhotoshopDirectory(string? JpegQuality, + string? Url) +{ + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, PhotoshopDirectorySourceGenerationContext.Default.PhotoshopDirectory); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(PhotoshopDirectory))] +public partial class PhotoshopDirectorySourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Models/Exif/PngDirectory.cs b/Models/Exif/PngDirectory.cs new file mode 100644 index 0000000..8af281a --- /dev/null +++ b/Models/Exif/PngDirectory.cs @@ -0,0 +1,23 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace File_Folder_Helper.Models.Exif; + +public record PngDirectory(string? ImageHeight, + string? ImageWidth, + string? TextualData) +{ + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, PngDirectorySourceGenerationContext.Default.PngDirectory); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(PngDirectory))] +public partial class PngDirectorySourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Models/Exif/QuickTimeMovieHeaderDirectory.cs b/Models/Exif/QuickTimeMovieHeaderDirectory.cs new file mode 100644 index 0000000..d8da238 --- /dev/null +++ b/Models/Exif/QuickTimeMovieHeaderDirectory.cs @@ -0,0 +1,21 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace File_Folder_Helper.Models.Exif; + +public record QuickTimeMovieHeaderDirectory(DateTime? Created) +{ + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, QuickTimeMovieHeaderDirectorySourceGenerationContext.Default.QuickTimeMovieHeaderDirectory); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(QuickTimeMovieHeaderDirectory))] +public partial class QuickTimeMovieHeaderDirectorySourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Models/Exif/QuickTimeTrackHeaderDirectory.cs b/Models/Exif/QuickTimeTrackHeaderDirectory.cs new file mode 100644 index 0000000..230dc3f --- /dev/null +++ b/Models/Exif/QuickTimeTrackHeaderDirectory.cs @@ -0,0 +1,21 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace File_Folder_Helper.Models.Exif; + +public record QuickTimeTrackHeaderDirectory(DateTime? Created) +{ + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, QuickTimeTrackHeaderDirectorySourceGenerationContext.Default.QuickTimeTrackHeaderDirectory); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(QuickTimeTrackHeaderDirectory))] +public partial class QuickTimeTrackHeaderDirectorySourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Models/Exif/WebPDirectory.cs b/Models/Exif/WebPDirectory.cs new file mode 100644 index 0000000..99873de --- /dev/null +++ b/Models/Exif/WebPDirectory.cs @@ -0,0 +1,22 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace File_Folder_Helper.Models.Exif; + +public record WebPDirectory(string? ImageHeight, + string? ImageWidth) +{ + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, WebPDirectorySourceGenerationContext.Default.WebPDirectory); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(WebPDirectory))] +public partial class WebPDirectorySourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Models/Face/FaceEncoding.cs b/Models/Face/FaceEncoding.cs new file mode 100644 index 0000000..7349861 --- /dev/null +++ b/Models/Face/FaceEncoding.cs @@ -0,0 +1,11 @@ +using System.Text.Json.Serialization; + +namespace File_Folder_Helper.Models.Face; + +public record FaceEncoding(double[] RawEncoding, int Size); + +[JsonSourceGenerationOptions(WriteIndented = false)] +[JsonSerializable(typeof(FaceEncoding))] +public partial class FaceEncodingGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Models/Face/FaceFile.cs b/Models/Face/FaceFile.cs new file mode 100644 index 0000000..c15801a --- /dev/null +++ b/Models/Face/FaceFile.cs @@ -0,0 +1,33 @@ +using System.Text.Json.Serialization; + +namespace File_Folder_Helper.Models.Face; + +public record FaceFile(int? AreaPermyriad, + int? ConfidencePercent, + string? DMS, + DateTime DateTime, + FaceEncoding? FaceEncoding, + Dictionary? FaceParts, + Location? Location, + string? Maker, + MappingFromPerson? MappingFromPerson, + string? Model, + OutputResolution? OutputResolution); + +[JsonSourceGenerationOptions(WriteIndented = false)] +[JsonSerializable(typeof(FaceFile))] +public partial class FaceFileGenerationContext : JsonSerializerContext +{ +} + +[JsonSourceGenerationOptions(WriteIndented = false, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] +[JsonSerializable(typeof(FaceFile[]))] +public partial class FaceFileCollectionGenerationContext : JsonSerializerContext +{ +} + +[JsonSourceGenerationOptions(WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] +[JsonSerializable(typeof(FaceFile[]))] +public partial class FaceFileCollectionWriteIndentedGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Models/Face/FacePart.cs b/Models/Face/FacePart.cs new file mode 100644 index 0000000..26fb934 --- /dev/null +++ b/Models/Face/FacePart.cs @@ -0,0 +1,54 @@ +namespace File_Folder_Helper.Models.Face; + +/// +/// Specifies the part of face. +/// +public enum FacePart +{ + + /// + /// Specifies the chin. + /// + Chin = 0, + + /// + /// Specifies the left eyebrow. + /// + LeftEyebrow = 17, + + /// + /// Specifies the right eyebrow. + /// + RightEyebrow = 22, + + /// + /// Specifies the nose bridge. + /// + NoseBridge = 27, + + /// + /// Specifies the nose tip. + /// + NoseTip = 31, + + /// + /// Specifies the left eye. + /// + LeftEye = 36, + + /// + /// Specifies the right eye. + /// + RightEye = 42, + + /// + /// Specifies the top lip. + /// + TopLip = 48, + + /// + /// Specifies the bottom lip. + /// + BottomLip = 55 + +} \ No newline at end of file diff --git a/Models/Face/FacePoint.cs b/Models/Face/FacePoint.cs new file mode 100644 index 0000000..17d5ea6 --- /dev/null +++ b/Models/Face/FacePoint.cs @@ -0,0 +1,39 @@ +using System.Drawing; +using System.Text.Json.Serialization; + +namespace File_Folder_Helper.Models.Face; + +[method: JsonConstructor] +public class FacePoint(int index, int x, int y) +{ + public int Index { get; } = index; + public int X { get; } = x; + public int Y { get; } = y; + + private readonly Point _Point = new(x, y); + + public override bool Equals(object? obj) => obj is FacePoint point && Equals(point); + +#pragma warning disable IDE0070 + public override int GetHashCode() +#pragma warning restore IDE0070 + { + int hashCode = 1861411795; + hashCode = (hashCode * -1521134295) + _Point.GetHashCode(); + hashCode = (hashCode * -1521134295) + Index.GetHashCode(); + return hashCode; + } + + public bool Equals(FacePoint? facePoint) + { + return facePoint is not null + && X == facePoint.X + && Y == facePoint.Y + && Index == facePoint.Index; + } + + public static bool operator ==(FacePoint point1, FacePoint point2) => point1.Equals(point2); + + public static bool operator !=(FacePoint point1, FacePoint point2) => !(point1 == point2); + +} \ No newline at end of file diff --git a/Models/Face/Location.cs b/Models/Face/Location.cs new file mode 100644 index 0000000..75570d2 --- /dev/null +++ b/Models/Face/Location.cs @@ -0,0 +1,42 @@ +using System.Text.Json.Serialization; + +namespace File_Folder_Helper.Models.Face; + +[method: JsonConstructor] +public class Location(int bottom, double confidence, int left, int right, int top) : IEquatable +{ + + public int Bottom { init; get; } = bottom; + public double Confidence { init; get; } = confidence; + public int Left { init; get; } = left; + public int Right { init; get; } = right; + public int Top { init; get; } = top; + + public override bool Equals(object? obj) => Equals(obj as Location); + +#pragma warning disable IDE0070 + public override int GetHashCode() +#pragma warning restore IDE0070 + { + int hashCode = -773114317; + hashCode = (hashCode * -1521134295) + Bottom.GetHashCode(); + hashCode = (hashCode * -1521134295) + Left.GetHashCode(); + hashCode = (hashCode * -1521134295) + Right.GetHashCode(); + hashCode = (hashCode * -1521134295) + Top.GetHashCode(); + return hashCode; + } + + public bool Equals(Location? location) + { + return location is not null + && Bottom == location.Bottom + && Left == location.Left + && Right == location.Right + && Top == location.Top; + } + + public static bool operator ==(Location location1, Location location2) => EqualityComparer.Default.Equals(location1, location2); + + public static bool operator !=(Location location1, Location location2) => !(location1 == location2); + +} \ No newline at end of file diff --git a/Models/Face/MappingFromPerson.cs b/Models/Face/MappingFromPerson.cs new file mode 100644 index 0000000..531c4e6 --- /dev/null +++ b/Models/Face/MappingFromPerson.cs @@ -0,0 +1,24 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace File_Folder_Helper.Models.Face; + +public record MappingFromPerson(int? ApproximateYears, + string DisplayDirectoryName, + long PersonKey, + string SegmentB) +{ + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, MappingFromPersonGenerationContext.Default.MappingFromPerson); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(MappingFromPerson))] +public partial class MappingFromPersonGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Models/Face/OutputResolution.cs b/Models/Face/OutputResolution.cs new file mode 100644 index 0000000..204ef80 --- /dev/null +++ b/Models/Face/OutputResolution.cs @@ -0,0 +1,5 @@ +namespace File_Folder_Helper.Models.Face; + +public record OutputResolution(int Height, + int Orientation, + int Width); \ No newline at end of file diff --git a/Worker.cs b/Worker.cs index 2330312..8bc6dab 100644 --- a/Worker.cs +++ b/Worker.cs @@ -136,6 +136,7 @@ public class Worker : BackgroundService break; } } + string extension = Path.GetExtension(_Args[0]); if (consoleKey is not ConsoleKey.End && !_ConsoleKeys.Contains(consoleKey)) consoleKey = ConsoleKey.End; if (singleCharIndex is not null) @@ -233,6 +234,8 @@ public class Worker : BackgroundService break; } } + else if (_AppSettings.ValidImageFormatExtensions.Contains(extension) && File.Exists(_Args[0])) + Helpers.Exif.HelperExif.DragAndDrop(_Logger, _Args[0]); else throw new Exception(_Args[0]); }